DateRangePicker

A span of days, from one day to another (date range): leave, a sprint, a reporting period. Two months side by side, presets for the usual spans, and the days counted while the pointer moves.

import { DateRangePicker } from "@umriss-ui/core";

Both ends lie at local midnight and both count. The value contract of the pickers stands on DatePicker.

The same { from, to } shape is what @umriss-ui/table reads a date filter from.

Examples

A span of days

Two clicks set both ends, in either order; the value is { from, to }, both inclusive, at local midnight.

import { useState } from "react";
import { DateRangePicker, FormField, Stack, Text, rangeDays, useFormats } from "@umriss-ui/core";
import type { DateRange } from "@umriss-ui/core";

export default function ASpanOfDays() {
  const formats = useFormats();
  const [leave, setLeave] = useState<DateRange | null>(null);

  return (
    <Stack gap={3} style={{ maxWidth: 340 }}>
      <FormField label="Leave">
        <DateRangePicker value={leave} onChange={setLeave} clearable />
      </FormField>
      <Text size="xs" tone="muted" mono>
        {leave === null
          ? "value: null"
          : `${formats.date(leave.from)} to ${formats.date(leave.to)}, ${rangeDays(leave.from, leave.to)} days`}
      </Text>
    </Stack>
  );
}

Presets

Pass presets for the spans your users pick most; an empty list hides the column, and none gives the defaults.

import { useState } from "react";
import { DateRangePicker, FormField, Grid } from "@umriss-ui/core";
import type { DateRange } from "@umriss-ui/core";

const fromToday = (days: number): DateRange => {
  const today = new Date();
  const from = new Date(today.getFullYear(), today.getMonth(), today.getDate());
  return { from, to: new Date(from.getFullYear(), from.getMonth(), from.getDate() + days - 1) };
};

export default function Presets() {
  const [sprint, setSprint] = useState<DateRange | null>(null);
  const [workshop, setWorkshop] = useState<DateRange | null>(null);
  const [report, setReport] = useState<DateRange | null>(null);

  return (
    <Grid minItemWidth="240px" gap={4}>
      <FormField label="Sprint" hint="Presets of your own.">
        <DateRangePicker
          value={sprint}
          onChange={setSprint}
          clearable
          placeholder="Choose a sprint"
          presets={[
            { label: "Next 2 weeks", range: () => fromToday(14) },
            { label: "Next 3 weeks", range: () => fromToday(21) },
          ]}
        />
      </FormField>
      <FormField label="Workshop" hint="No presets.">
        <DateRangePicker value={workshop} onChange={setWorkshop} presets={[]} />
      </FormField>
      <FormField label="Report" hint="The default presets.">
        <DateRangePicker value={report} onChange={setReport} />
      </FormField>
    </Grid>
  );
}

States

size="sm" suits dense forms; errors come from the FormField, and disabled locks field and calendar.

import { useState } from "react";
import { DateRangePicker, FormField, Grid } from "@umriss-ui/core";
import type { DateRange } from "@umriss-ui/core";

export default function States() {
  const [training, setTraining] = useState<DateRange | null>({ from: new Date(2026, 3, 13), to: new Date(2026, 3, 14) });
  const [leave, setLeave] = useState<DateRange | null>({ from: new Date(2026, 2, 16), to: new Date(2026, 2, 27) });

  return (
    <Grid minItemWidth="240px" gap={4}>
      <FormField label="Training" hint="Small size.">
        <DateRangePicker size="sm" value={training} onChange={setTraining} clearable />
      </FormField>
      <FormField label="Leave" error="Chloe Durand is already on leave from 23 March.">
        <DateRangePicker value={leave} onChange={setLeave} clearable />
      </FormField>
      <FormField label="Sprint 14" hint="Set by the sprint plan.">
        <DateRangePicker value={{ from: new Date(2026, 2, 9), to: new Date(2026, 2, 20) }} onChange={() => {}} disabled />
      </FormField>
    </Grid>
  );
}

With an error

A FormField with an error marks the field invalid by itself. Without one, set invalid and tie your message to the field with aria-describedby.

import { useId, useState } from "react";
import { DateRangePicker, Stack, Text } from "@umriss-ui/core";
import type { DateRange } from "@umriss-ui/core";

/* Chloe Durand covers for Noah Fischer and is away herself from 23 March. */
const AWAY_FROM = new Date(2026, 2, 23);

export default function WithAnError() {
  const [leave, setLeave] = useState<DateRange | null>({ from: new Date(2026, 2, 16), to: new Date(2026, 2, 27) });
  const messageId = useId();
  const error = leave !== null && leave.to >= AWAY_FROM ? "Chloe Durand, who covers for you, is on leave from 23 March." : undefined;

  return (
    <Stack gap={1} style={{ maxWidth: 360 }}>
      <DateRangePicker
        aria-label="Leave, Noah Fischer"
        value={leave}
        onChange={setLeave}
        invalid={error !== undefined}
        aria-describedby={error ? messageId : undefined}
      />
      {error && (
        <Text id={messageId} size="xs" style={{ color: "var(--u-color-danger-text)" }}>
          {error}
        </Text>
      )}
    </Stack>
  );
}

When to use something else

Keyboard

Key Action
Arrow keys Move a day or a week, across both months.
Enter or Space Sets the start, then the end.
Escape Closes the panel.

API

DateRangePickerProps

NameTypeDefaultDescription
valuerequiredDateRange | null—The time span, both ends on local midnight and both inclusive; null means none.

Shown in: Configurator, A span of days, Presets and 3 more

placeholderstring—What stands in the empty field.

Shown in: Presets

disabledbooleanfalseLocks the field and the panel.

Shown in: Configurator, States

invalidboolean—Marks the field as invalid. FormField sets it itself as soon as it carries an error - by hand only necessary without FormField.

Shown in: Configurator, With an error

size"sm" | "md"the size of a ControlSizeProvider around it, else "md"sm for a toolbar and dense forms, md otherwise.

Shown in: Configurator, States

charsnumber—The width in characters - room for the value; the field adds its own padding, glyph and cross. Given, the field is that wide wherever it stands, and never wider than its place. Without it the field fills its place, and where the place asks - in a toolbar or a row - it is as wide as its longest value in the formats in use, so a date never truncates.

Shown in: Configurator

clearablebooleanfalseShows a cross where a value is set, which fades in on hover/focus and clears the value.

Shown in: Configurator, A span of days, Presets and 2 more

presetsRangePreset[]DEFAULT_PRESETS, in the wording's labelsThe quick-select column in the panel. An empty array hides the column.

Shown in: Presets

refRef<HTMLSpanElement>—Receives the component's root element. from RefProp

Shown in: A row that holds still (Sizes), An anchored surface (Popover), Take the width of the trigger (Popover) and 2 more

Events

NameTypeDefaultDescription
onChangerequired(range: DateRange | null) => void—Runs as soon as both ends are settled - after the second click, on a preset and on clearing (null). Ends dragged backwards swap silently; there is no error state to handle.

Shown in: Configurator, A span of days, Presets and 3 more

Also takes every attribute of <span> – without defaultValue, onChange.

Types on this page

DateRange

A span between two dates, both ends included - the value of DateRangePicker and DateTimeRangePicker.

NameTypeDefaultDescription
fromrequiredDate—

Shown in: A span of days, Presets, States and 6 more

torequiredDate—

Shown in: A span of days, Presets, States and 6 more

RangePreset

A shortcut in a range picker's panel: its label, and a function that computes the range when it is chosen - so that "Last 7 days" is counted from the moment of the click.

NameTypeDefaultDescription
labelrequiredstring—

Shown in: Presets

rangerequired() => DateRange—

Shown in: Presets

Known limits

What umriss deliberately does not build, and why: ADR-0032.

Every page of @umriss-ui/core

Getting started

Customising

Layout

Typography

Actions

Forms

Choosing

Dates and times

Feedback

Overlays

Navigation

Data display

API index