Skip to content

DateRangePicker

Terminal window
$ npx shadcn@latest add https://lacodda.github.io/dowel/r/date-range-picker.json
See it liveEmpty, half made, whole, and bounded.→

The state between the two clicks is the component. After the first click there is a start and no end — not an incomplete value to hide, and not a range of one day. It is the normal middle of the interaction, and the reader has to see it: the first day marked, the popup still open, the trigger already saying something.

<DateRangePicker
value={period} // { start: '2026-09-02' } while deciding
onValueChange={setPeriod} // fires on both clicks
placeholder="Pick a range"
previousMonthLabel="Previous month"
nextMonthLabel="Next month"
/>

onValueChange firing on the first click is what lets a product show “from 2 September” while the reader is still choosing the other end. A picker that reports nothing until the range is whole appears to do nothing at all.

The clicks can come in either order. Clicking the 20th and then the 10th means the 10th to the 20th, because that is plainly what was meant. Refusing it would be correct and unhelpful.

Clicking again starts over. Once a range is whole, the next click is a new start rather than an edit of the old end — which is what a reader means by clicking a third time.

A range can be taken back. Give clearLabel and, while anything is chosen

  • half made or whole - the popup offers it under the month, the way DatePicker does; onValueChange is told the empty range, {}.

Props

Read from the types in ui/date-range-picker.tsx, the file a product copies - so the description of each prop is also what an editor shows over it there.

PropTypeDefault
valueDateRangeThe chosen range. Either end may be absent: a start with no end is the range while it is half made.
onValueChange(value: DateRange) => voidCalled on both clicks, so a product sees the half-made range as well as the whole one.
clearLabelstringThe words of the button that takes the range back: "Clear". Leave it out and a chosen range can only be replaced, not emptied.
minstringThe earliest day that can be chosen, as YYYY-MM-DD.
maxstringThe latest day that can be chosen, as YYYY-MM-DD.
placeholderrequiredstringWhat the trigger says when nothing is chosen.
previousMonthLabelrequiredstringNames the two month-paging buttons inside the calendar.
nextMonthLabelrequiredstringNames the button that pages to the next month, for the same reason as previousMonthLabel.
localestringThe language the dates and the calendar are written in. Defaults to the locale from useLocale.
disabledbooleanfalseStops the trigger opening the calendar.
idstringPut on the trigger, so a Field label can point at it.
aria-labelstringNames the trigger when there is no visible label. The trigger's name is this and then the date it shows.
aria-labelledbystringPoints the trigger at its visible label. The date it shows is added to the name.
aria-describedbystringPoints the trigger at a hint or an error message.
aria-invalidtrue | false | booleanMarks the trigger as holding a value that is not accepted.
classNamestring

Accessibility

The examples on the stand, through axe 4.14.0 in a browser, in both themes, against WCAG 2.2 A and AA and axe's best practices: 15 rules applied and passed, and none failed. The accessibility report has every item.

History

Each release that changed ui/date-range-picker.tsx - the file a product copies - newest first.

  1. v0.41.0
    • docs read every props table from the component's types ee3b159
  2. v0.40.0
    • refactor say state on the element, and measure the palette's anchor apart 8d9b4e1
    • fix put the calendar's cursor on the focus, and open a picker on the day fe23f69
  3. v0.38.0breaking
    • feat take a chosen date back 13d7dd1
  4. v0.37.0
    • fix name the trigger by its label and its date 96662d8
  5. v0.31.0breaking
    • feat dates and numbers in the application's language 9be5f95
  6. v0.28.0breaking
    • feat density as a property of a region 849d783
  7. v0.18.1
    • feat dates, on Intl rather than on a date library 5ec8403