Date range picker
A range you can type or pick. It reads “last 7 days” or “q3”, keeps rolling ranges rolling, and compares with the period before.
Value: {"from":"2026-10-01","to":"2026-10-31","preset":"Last 30 days","compare":"period"}
Installation
npx shadcn@latest add @quireui-pro/date-range-pickerPart of Pro. Add the Pro registry with your token once, as shown in Installation, and the CLI brings in the free parts it builds on as well.
Usage
const [period, setPeriod] = useState<DateRangeValue | null>(null);
<DateRangePicker aria-label="Report period" value={period} onValueChange={setPeriod} />The value is { from, to } as plain dates, like { from: "2026-10-01", to: "2026-10-31" }, counting both ends. No time and no timezone, so a range never moves by a day between the browser, the server and the database.
Typing
A range is often quicker to type than to click. While you type, the field shows what it read and how long it is, "→ Oct 1 – 20 · 20 days"; Enter takes it, Esc puts back what was there.
| You type | It reads |
|---|---|
last 7 days, past 2 weeks, next 30 days | Counted from today, today included |
this month, last month, this quarter, last year | That whole period |
ytd, mtd | From the start of the year or month to today |
q3, q1 2027, october, 2025 | That quarter, month or year |
oct 1 - oct 20, oct 1-20, 3/10 to 3/20 | Those two days, in either order |
today, next fri | One day, as a range of one |
Picking
The calendar opens from the button or with the down arrow. Pick the first day, then point at or arrow to the last: the range previews as you go and the footer says how long it is. The second pick takes it. Two months show side by side where there is room, one on a phone.
Presets sit beside the months and take a range in one click; the one that matches the value is checked. Pass presets to choose your own: phrases the field understands work as they are, anything else takes a label and a function of today.
<DateRangePicker
presets={[
"Last 7 days",
"This quarter",
{ label: "Since launch", range: (today) => ({ from: "2026-03-01", to: today }) },
]}
/>Rolling ranges
A saved "Last 7 days" should still mean the last 7 days tomorrow. With rolling, a preset or a typed phrase that moves with the calendar stays in the value as preset, and the field shows the phrase with its days beside it. Typed dates and named periods like "q3" stay fixed.
<DateRangePicker rolling value={view.period} onValueChange={savePeriod} />
// { from: "2026-10-23", to: "2026-10-29", preset: "Last 7 days" }Wherever the dates are read, like a server loading a saved report, resolveRange moves a rolling range to the days it covers today. It is not a client function, so it runs on the server too.
import { resolveRange } from "@/components/ui/date-range";
const { from, to } = resolveRange(view.period, today);Comparing
compare adds "Compare with" under the presets: the previous period, the same number of days just before (whole months compare with whole months, so March with February), or the previous year. The choice is kept in the value as compare. The compared days get a line under them in the calendar, and the footer names them.
comparedRange(value) gives the compared days, for the query that loads them. With name, they go with the form as {name}CompareFrom and {name}CompareTo.
Apply
By default a choice takes effect at once: the second day, a preset, a comparison. Half a range is never sent, since the first pick only waits for the second. Where every change loads something heavy, apply holds what you pick until Apply is pressed; Cancel, Esc or clicking away keep the range that was there.
Reports loaded: 0
Limits and bookings
min and max bound the days, and isDateDisabled rules out others. minDays and maxDays bound the length, counting both ends. For bookings, spanDisabled={false} stops a range from running across a disabled day. Once the first day is picked, every day that could not end the range fades.
<DateRangePicker presets={[]} minDays={3} maxDays={15} spanDisabled={false} isDateDisabled={isBooked} />The panel on its own
DateRangePanel is the inside of the picker (the presets, the months and the footer) for a popover of your own. With typing it gets a field to type into on top. The Data table's date filter is built this way.
<PopoverContent size="auto" className="p-0">
<DateRangePanel typing rolling value={range} onValueChange={setRange} onDone={() => setOpen(false)} />
</PopoverContent>Other languages
locale formats the dates and the calendar, picks the first day of the week, and decides what 3/10 means. labels translates its own words. Typed phrases like "last 7 days" are read in English.
Forms
With name, the range travels with its form as {name}From and {name}To in YYYY-MM-DD, through hidden inputs, so it works with plain form posts and server actions. A rolling range sends the days it covers today.
Accessibility
The field is a text input with a name from aria-label or a linked label, and what it read is announced politely as you type. Each month is a grid of buttons named in full, "Friday, October 23, 2026", with the days in the range marked as selected. The presets and the comparison are groups that Tab reaches once; the arrow keys move inside them. The footer announces the first day, then the range and its length.
| Key | Action |
|---|---|
| ↓ | In the field: open the calendar |
| Enter | In the field: take the typed range. In the calendar: pick the first or the last day |
| Esc | In the field: put back the last range. In the calendar: close it |
| ← / → | Previous or next day; in the presets, the previous or next one |
| ↑ / ↓ | Same day last or next week; in the presets, the previous or next one |
| Page Up / Page Down | Same day last or next month; with Shift, last or next year |
| Home / End | First or last day of the week, or the first or last preset |
Motion
The calendar opens with the library's popover motion. Months turned with the arrows slide in from that side in 160ms; turned with the keys, they change instantly, and with reduced motion they only fade. The range follows the pointer exactly, with no easing, so it never lags behind where you point.
Date range picker or something else?
- One date: the free Date picker.
- A month or a year on its own: a select is quicker than a calendar.
API reference
DateRangePanel
The inside of the range picker on its own: presets, months that preview the range as you point, and a footer with its length. Put it in your own popover, like a filter's.
Prop
Type
Style it with [data-slot="date-range-panel"].
DateRangePicker
A range of dates you can type or pick. Typing reads "last 7 days", "this month", "q3" and "oct 1-20"; the calendar shows two months with presets beside them, and previews the range as you point at its last day.
Prop
Type
Style it with [data-slot="date-range-picker"].