A button showing a date range, opening presets, typed dates and a calendar.
import { DateRangePicker } from '@oztix/roadie-components/date-range-picker'
Press the button and choose a preset, type a start and an end, or press two days in the calendar. The Start and End fields suggest single dates as you type, as DateField does. They never suggest a range, since the presets hold those, and End suggests nothing before the start. A preset keeps its words, so "Last 30 days" saved today still means the last 30 days next week, and arrives in its one form: { period: 'week', offset: -1 } as 'last-week'. Typed or pressed dates become an absolute range, { start, end }, with both ends included. The button shows the range in words, with the dates a relative range stands for beside it.
commit='apply' holds every change until Apply is pressed, and Cancel or Escape drops them. Use it when a change refetches a whole page, such as a dashboard's period. The default, immediate, changes the value with each choice, as a filter does.
granularity='minute' adds an optional time to each end, read on the clock of timeZone. An end with a time becomes an instant with its offset, such as '2026-11-02T09:00:00+11:00'; an end with no time covers its whole day.
readOnly shows the range with a lock and doesn't open, for a period the page has set. disabled turns the button off.
Wrap it in Field. The label names the button, the helper or error text describes it, and required adds “Required” to its description. Typed start and end dates explain their own errors inside the popup. The value is an object, so the picker holds it as state and submits nothing with a form; send it from your own handler.
For a dashboard, use Dashboard period: this picker with a Compare switch under the range, applied with it. Elsewhere, put a separate control beside the picker, such as a Select. Resolve both with resolveDateRange and resolveComparison from @oztix/roadie-core/datetime, passing the picker's timeZone, weekStart and fiscalYearStart, so they agree on what the words mean. A benchmark that isn't a date, such as similar venues, belongs in its own control.
presets replaces the list, and group sets presets under a heading. Presets with no group come first, then each group in the order it first appears. Spread dateRangePresets to add to the defaults instead. A preset's label defaults to its value in words. When the value is a preset, fixed dates or relative, the button shows the preset's label with its dates, and so does the list on a phone. Here Xylophone Tide Festival adds its on-sale week, and a dashboard keeps the periods it reports on.
disabled takes the same matchers as Calendar. Matched days are disabled in the calendar and refused when typed. min and max limit the length in days, both ends counted, and typed dates that break them say so. Presets aren't checked against either, so leave out presets that reach matched days or break the length. Here sales data starts on 1 July 2026.
saveView({ period: range })
Do
Save the value as it comes. "Last 30 days" stays the last 30 days, and the button shows the dates it stands for today.
saveView({ period: resolveDateRange(range, options) })
Don’t
Don't save resolved dates for a relative choice. The view stops moving with time.
Do
Keep presets as you'd show them on a wide screen. On a phone the picker puts them on their own Periods tab, each with its dates, and the calendar on another.
Don’t
Don't build a separate phone picker from chips or a second calendar. The drawer already gives each its own room.
Do
Use commit='apply' for a dashboard period, where every card refetches.
Don’t
Don't make a filter wait for Apply. Filters change the list as each choice is made.
Escape closes and puts focus back on the button. With commit='apply' it drops the changes; in immediate mode, dates already chosen or committed stay.aria-pressed, in groups named by their headings. Custom range is pressed when the value is no preset, and moves focus to the start date.Enter takes it, and Escape closes the list and keeps the text. In the drawer the list opens above it, so nothing clips it.Enter commits typed text, and Escape puts back the last date. An end before the start says "Ends before it starts".Page Up, Page Down, Home and End move, and Enter or Space chooses. Escape after the first day of a range drops it and keeps the dates you had.aria-current and a check.min or max, and the End field says why.commit='apply', Clear and Apply stay in view at the drawer's foot. Close, Escape, a tap outside or a swipe down closes it, and with commit='apply' drops the changes, as Cancel does in the popover.A button showing a date range, opening presets, typed dates and a calendar.
The range: absolute `{ start, end }` with inclusive ISO dates (or date-times at `minute` granularity), or a relative range such as `'last-week'` or `{ direction: 'past', amount: 30, unit: 'day' }`. Pair with `onValueChange`.
The range to start from when uncontrolled.
Called with a preset's value, relative and in its one form (`canonicalDateRange`), or with absolute dates when they are chosen or typed. Null when both dates are cleared.
Choices beside the calendar, grouped by their `group`. Pass your own, or add to `dateRangePresets`. An empty list hides it.
Defaults to dateRangePresets.
`immediate` changes the value with each choice. `apply` holds changes until Apply is pressed, for a period that refetches a whole page.
Defaults to 'immediate'.
`minute` adds an optional time to each end. An end with no time covers its whole day.
Defaults to 'day'.
IANA zone whose calendar decides today and whose wall clock times are read on. Defaults to the viewer's.
Today as an ISO date. Defaults to today in `timeZone`.
First day of the week, for the calendar and "last week": 1 is Monday and 7 is Sunday.
Defaults to 1.
The month the financial year opens, for financial presets.
Defaults to 7.
Defaults to 'en-AU'.
`true` turns the picker off. Matchers, as on `Calendar`, disable those days in the calendar and refuse them when typed. Inherits `true` from `Field`.
Marks the picker as invalid. Inherits from `Field` when omitted.
Shows the range with a lock, without letting it change.
With `commit='apply'`, keeps Apply off while both dates are empty. Inherits from `Field` when omitted.
Defaults to 'md'.
Defaults to 'normal'.
Shown while no range is chosen.
Defaults to 'Choose dates'.
Months side by side. Defaults to two on wide screens and one on narrow.
The fewest days the range can span, both ends counted.
The most days the range can span, both ends counted.
`dropdown` swaps the calendar's month name for month and year selects.
Defaults to 'label'.
The earliest month the calendar reaches, as any ISO date in it.
The latest month the calendar reaches, as any ISO date in it.
At `minute` granularity, `12` reads "7:30pm" and `24` reads "19:30".
Defaults to 12.
At `minute` granularity, minutes per step of the time fields' arrow keys.
Defaults to 1.
Whether the popup is open. Pair with `onOpenChange`.
Whether the popup starts open when uncontrolled.
Called when the popup opens or closes.