Date range picker
ProA trigger showing the selected range that opens presets and a two-month calendar, with an optional compare to previous period toggle. Changes apply on Apply.
npx prodular-ui add date-range-pickerPart of Prodular UI Pro. Preview and read the docs freely, install with a license. See pricing
Reporting period with compare
The trigger shows the comparison period on wide screens. Future dates are disabled.
Revenue for Sep 22 – Oct 21, 2026, compared with Aug 23 – Sep 21, 2026
"use client"
import * as React from "react"
import { DateRangePicker, formatDateRange, getPreviousPeriod, type DateRange } from "@/components/ui/date-range-picker"
// A fixed reference date keeps presets stable between server and client.const today = new Date(2026, 9, 21)
export default function DateRangePickerCompare() { const [range, setRange] = React.useState<DateRange | undefined>({ from: new Date(2026, 8, 22), to: new Date(2026, 9, 21), }) const [compare, setCompare] = React.useState(true) const previous = range?.from && range.to ? getPreviousPeriod({ from: range.from, to: range.to }) : undefined
return ( <div className="flex flex-col items-center gap-3"> <DateRangePicker value={range} onValueChange={setRange} comparable compare={compare} onCompareChange={setCompare} today={today} disabledDates={{ after: today }} align="start" aria-label="Reporting period" /> <p className="text-[13px] text-muted-foreground tabular-nums"> Revenue for {formatDateRange(range)} {compare && previous ? `, compared with ${formatDateRange(previous)}` : ""} </p> </div> )}Custom presets
Replace the preset list with periods that fit the product. Custom is always added at the end.
"use client"
import { endOfDay, startOfDay, startOfMonth, subDays, subMonths, endOfMonth } from "date-fns"
import { DateRangePicker, type DateRangePreset } from "@/components/ui/date-range-picker"
const today = new Date(2026, 9, 21)
const presets: DateRangePreset[] = [ { id: "24h", label: "Last 24 hours", range: (t) => ({ from: startOfDay(subDays(t, 1)), to: endOfDay(t) }) }, { id: "14d", label: "Last 14 days", range: (t) => ({ from: startOfDay(subDays(t, 13)), to: endOfDay(t) }) }, { id: "mtd", label: "Month to date", range: (t) => ({ from: startOfMonth(t), to: endOfDay(t) }) }, { id: "last-month", label: "Last month", range: (t) => ({ from: startOfMonth(subMonths(t, 1)), to: endOfMonth(subMonths(t, 1)) }), },]
export default function DateRangePickerPresets() { return ( <div className="flex flex-col items-center gap-2"> <label htmlFor="log-range" className="text-sm font-medium"> Log retention window </label> <DateRangePicker id="log-range" presets={presets} today={today} disabledDates={{ after: today }} placeholder="Any time" className="w-72" /> </div> )}Anatomy
<DateRangePicker value={range} onValueChange={setRange} comparable compare={compare} onCompareChange={setCompare}/>Guidance
Use it when
- Analytics and reports where people switch between standard periods and sometimes pick exact dates.
- comparable on dashboards that show deltas, so the comparison period is explicit.
Avoid
- Picking a single date. Use Calendar in a Popover.
- Very long ranges spanning years. Add year presets or a year select instead of paging months.
Rules for AI agents
Published in llms-full.txt, the registry and the MCP server, so coding agents follow them when they build with this component.
- Pass a fixed today when rendering on the server or in tests, and disabledDates={{ after: today }} when future data cannot exist.
- Treat onValueChange as the commit. It fires on Apply, not on every click in the calendar.
- Use getPreviousPeriod(range) to compute the comparison window for your query instead of recomputing it yourself.
- Pass presets that match the product's reporting periods. Keep them short, five at most.
- Give the trigger an aria-label or a visible label when the context does not name the period.
API
DateRangePicker
| Prop | Type | Default | Description |
|---|---|---|---|
| value / defaultValue / onValueChange | DateRange | undefined | { from, to }. onValueChange fires on Apply. | |
| comparable | boolean | false | Shows the Compare to previous period switch. |
| compare / defaultCompare / onCompareChange | boolean | Compare state, committed with Apply. | |
| presets | DateRangePreset[] | Today, Last 7 days, Last 30 days, This quarter, Year to date | { id, label, range(today) }. Pass [] to hide the column. |
| today | Date | new Date() | Reference date for presets. |
| numberOfMonths | number | 2 | Months shown side by side. |
| disabledDates | Matcher | react-day-picker matcher, for example { after: today }. |
getPreviousPeriod
| Prop | Type | Default | Description |
|---|---|---|---|
| (range) | function | Same-length period ending the day before range.from. |
formatDateRange
| Prop | Type | Default | Description |
|---|---|---|---|
| (range) | function | Formats as Sep 22 – Oct 21, 2026. |
Installation
The Prodular UI CLI adds the source to components/ui with everything it depends on. In a project that already uses shadcn/ui, the shadcn CLI installs it from the Prodular registry too. See Installation for the first setup.
npx prodular-ui add date-range-picker# or, with the registry in components.jsonnpx shadcn@latest add @prodular/date-range-picker