Components · Search and discovery
DateRangePicker
Start and end dates: typed or picked on a one- or two-month calendar with a live preview, presets and length limits.
Report period with presets
2026-10-06 to 2026-11-04; at most 366 days.
import { useState } from 'react';
import { Field, FieldLabel, FieldDescription } from '@rookcom/monarch-react';
import { DateRangePicker, type DateRange } from '@rookcom/monarch-react/scheduling';
const TODAY = '2026-11-04';
function ReportRange() {
const [value, setValue] = useState<DateRange | null>({ start: '2026-10-06', end: '2026-11-04' });
return (
<Field>
<FieldLabel>Report period</FieldLabel>
<DateRangePicker
locale="en-GB"
today={TODAY}
max={TODAY}
maxDays={366}
presets={[
{ label: 'Last 7 days', lastDays: 7 },
{ label: 'Last 30 days', lastDays: 30 },
{ label: 'Last 90 days', lastDays: 90 },
{ label: 'This month', months: 0 },
{ label: 'Last month', months: -1 },
]}
value={value}
onValueChange={setValue}
/>
<FieldDescription>
{value ? `${value.start} to ${value.end}` : 'No period'}; at most 366 days.
</FieldDescription>
</Field>
);
}
<ReportRange />Usage examples
Leave with a minimum length
import { Field, FieldLabel } from '@rookcom/monarch-react';
import { DateRangePicker } from '@rookcom/monarch-react/scheduling';
const TODAY = '2026-11-04';
<Field>
<FieldLabel>Leave</FieldLabel>
<DateRangePicker
locale="en-GB"
today={TODAY}
min={TODAY}
minDays={2}
defaultValue={{ start: '2026-11-12', end: '2026-11-12' }}
/>
</Field>Compact filter
import { DateRangePicker } from '@rookcom/monarch-react/scheduling';
const TODAY = '2026-11-04';
<DateRangePicker
aria-label="Created"
size="sm"
locale="en-US"
today={TODAY}
defaultValue={{ start: '2026-11-01', end: '2026-11-30' }}
/>Purpose
Picks a period such as a report range, a sprint, leave or a filter on created and due dates. Both ends can be typed in the locale’s format or picked on a calendar that previews the range as the pointer or focus moves; presets cover common periods. The value is { start, end }, inclusive ISO dates. Import from @rookcom/monarch-react/scheduling.
Use when
- Report and analytics periods with presets (Last 7 days, Last month).
- Filtering lists by a created, due or updated date range.
- Booking a span of days (leave, sprints, campaigns) with a minimum or maximum length.
Avoid when
- Only a few fixed periods and no custom range: use SegmentedControl.
- Two unrelated dates (a start date and a separate review date): use two DatePickers.
Design principles
- One control for one period: a single box with two inputs, one calendar, one error.
- The range is only committed when it is complete and valid, so filters never run on half a range.
- The calendar previews the range before the second click, and blocks days that would break the length limits.
- Two months where there is room, one on phones.
API reference
import { DateRangePicker } from '@rookcom/monarch-react/scheduling';| Export | Kind | Rendering | Root class |
|---|---|---|---|
DateRangePicker | component | client | .mn-date-range-picker |
The ref goes to the root role="group" element.
startName and endName add hidden inputs for form submission.
Generated from the TypeScript declarations of packages/react/src/components/search/date-range-picker.
Properties
value/onValueChange (controlled) or defaultValue; onValueChange receives a complete { start, end } or null.
Presets: { lastDays } (ending today), { nextDays } (starting today), { months } (0 = this calendar month, -1 = last), or a fixed { range }. Presets are clipped to min and max (with max today, “This month” is month to date) and disabled when what is left breaks the limits.
minDays/maxDays count both ends: Monday to Sunday is 7 days.
onValidationChange reports a date error, end-before-start, too-short, too-long or null.
DateRangePicker
Two dates that bound a period: typed in the locale's format, or picked on a one- or two-month calendar with a live preview of the range, plus quick picks such as "Last 7 days". Enforces min/max dates and a minimum and maximum length. The value is `{ start, end }` as ISO `YYYY-MM-DD`, inclusive.
| Prop | Type | Description |
|---|---|---|
children | ReactNode | |
className | string | |
clearable | boolean | Show a clear button while there is a value. Default true. |
defaultValue | DateRange | null | Initial range (uncontrolled). |
disabled | boolean | |
endName | string | Form field name for the end date. |
getDayInfo | ((date: IsoDate) => MiniMonthDayInfo | undefined) | Dots and descriptions for calendar days (see MiniMonth). |
id | string | |
invalid | boolean | Marks the field invalid. Inside a Field, the Field's `invalid` is used. |
isDateDisabled | ((date: IsoDate) => boolean) | Blocks extra dates as a start or an end. |
locale | string | BCP 47 locale. Default: the runtime's. Pass it for server rendering. |
max | string | Latest allowed date. |
maxDays | number | Longest allowed range in days, counting both ends (e.g. 366 for reports). |
messages | Partial<DateRangePickerMessages> | Text for translation. |
min | string | Earliest allowed date. |
minDays | number | Shortest allowed range in days, counting both ends. |
numberOfMonths | 1 | "auto" | 2 | Months shown in the calendar. `'auto'` (default) shows two on wide screens and one on narrow. |
onOpenChange | ((open: boolean) => void) | |
onValidationChange | ((error: DateRangeValidationError | null) => void) | Called when the built-in validation result changes (`null` when valid). |
onValueChange | ((value: DateRange | null) => void) | Called with a complete, valid range, or `null` when cleared. |
open | boolean | Whether the calendar is open (controlled). |
presets | readonly DateRangePreset[] | Quick picks such as "Last 7 days", shown beside the calendar. |
readOnly | boolean | |
required | boolean | |
showErrors | boolean | Render the built-in error message. Default true. |
showWeekNumbers | boolean | Show ISO week numbers in the calendar. |
size | "sm" | "md" | "lg" | Control height. Default `md`. |
startName | string | Form field name for the start date (`YYYY-MM-DD`). |
today | string | Today's date. Default: the device's. |
value | DateRange | null | The range (controlled), or `null` for none. |
weekStartsOn | Weekday | First day of the calendar week. Default: from the locale. |
Variants
| Prop | Values | Description |
|---|---|---|
numberOfMonths | 'auto''1''2' | auto shows two months from 640px wide, one below. Default auto. |
presets | 'none''list' | Quick picks beside the calendar (above it on phones). |
size | 'sm''md''lg' | Control height. Default md. |
States
| State | Exposed as | Behaviour |
|---|---|---|
| Picking | data-preview on calendar cells | After the first day: the start is marked and the range previews to the hovered or focused day. |
| Selected range | aria-selected="true", data-in-range | A tinted band with filled ends. |
| Invalid | aria-invalid="true", data-invalid | A date error, end before start, or a range too short or too long; one message explains it. |
| Disabled | disabled, data-disabled | Both inputs and the calendar button. |
Interaction behaviour
- Typed dates are read on blur and Enter; the value changes once both ends are valid.
- In the calendar, the first pick sets the start, the second the end; picking an earlier day second swaps them. The calendar then closes and focus returns to the start field.
- After the first pick a polite announcement says to choose the end date.
- A preset sets both ends at once and closes the calendar; the current preset is pressed.
- Escape closes the calendar and discards a half-picked range.
Keyboard support
| Keys | Action |
|---|---|
| Tab | Start field, end field, clear button, calendar button. |
| ↓ (in a field) | Open the calendar. |
| Arrows, Home/End, Page Up/Down | Move in the calendar; the preview follows focus. |
| Enter / Space | Pick the start, then the end. |
| Escape | Close the calendar. |
Accessibility
- Role
- group of two textboxes, with a button that opens a dialog containing one or two grids
- Accessible name
- The group is named by FieldLabel (or
aria-label); the inputs are "<label> start date" and "<label> end date"; the button "Choose dates" or "Change dates, <start> to <end>".
- Every day in the range is
aria-selected; preview days are not, so only the real selection is announced as selected. - Days blocked by the length limits are
aria-disabledwhile picking. - Presets are toggle buttons in a labelled group.
- One error message, linked to both inputs and announced politely.
Responsive behaviour
- The box fills its container; each input shrinks evenly.
- Two months side by side from 640px; one month below, so it fits a 320px screen.
- Below 640px, presets move above the calendar as wrapping chips.
Theme support
- Input tokens for the box and MiniMonth tokens for the calendar; the range band is accent-subtle.
- Forced-colours mode outlines the range and the pressed preset; reduced motion removes transitions.
Do and don't
Do
- Send
startandendas inclusive dates; if the API wants an exclusive end, add one day on the way out. - Set
maxDayswhen the backend limits the range, so the calendar blocks it up front. - Offer presets for the periods people use most.
Don't
- Do not run a query on every keystroke;
onValueChangeonly fires for a complete, valid range. - Do not convert the dates to local-time
Dateobjects to compute days; the strings are calendar dates.
Related components
- DatePicker: A single date.
- MiniMonth: The calendar grid it uses.
- FilterBar: Combine the range with other filters.
- SegmentedControl: A few fixed periods without a custom range.
Dependencies
Testing coverage
- Labelled group with start and end inputs and a dialog button; axe clean.
- Typed ranges commit only when both ends are valid; end before start and length errors.
- Calendar picking with hover preview, closing and returning focus; keyboard picking with swap and focus-driven preview.
- Days outside the length limits disabled while picking.
- Presets (
lastDays,nextDays,months,range), disabled when they break limits, pressed when current. - min/max dates; two months with a range across them.
- Clear, controlled value, hidden inputs, disabled.
| Check | Covered |
|---|---|
| Axe in jsdom | Yes |
| Server rendering | Yes |
| Browser suites | Visual baselines: /__gallery/date-range-picker and the open calendar (overlays/date-range-picker-*.png); Cross-browser axe: /components/date-range-picker |
Tests: packages/react/src/components/search/date-range-picker