Quire UI
Controls13

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-picker

Part 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 typeIt reads
last 7 days, past 2 weeks, next 30 daysCounted from today, today included
this month, last month, this quarter, last yearThat whole period
ytd, mtdFrom the start of the year or month to today
q3, q1 2027, october, 2025That quarter, month or year
oct 1 - oct 20, oct 1-20, 3/10 to 3/20Those two days, in either order
today, next friOne 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.

KeyAction
↓In the field: open the calendar
EnterIn the field: take the typed range. In the calendar: pick the first or the last day
EscIn 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 DownSame day last or next month; with Shift, last or next year
Home / EndFirst 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"].

On this page