Date range picker
A range picker that grows from its trigger into two months with presets and a stretching range highlight.
pnpm dlx shadcn@latest add @uiarc/date-range-pickerSite traffic
- Visitors
- 0
- Signups
- 0
- Conversion
- 0.0%
- Analytics and reporting filters that need presets like Last 7 days or This quarter.
- Booking or leave requests where a start and end day are picked together.
- Toolbars where the picker must stay compact until opened.
- Use date-picker for a single date.
- Use calendar when the month should stay visible on the page.
- Use time-picker when the user chooses a time of day rather than days.
Installation
Add Date range picker with the shadcn CLI, or copy the source by hand.
pnpm dlx shadcn@latest add @uiarc/date-range-pickerAdds the component and its local dependencies, and installs motion, lucide-react. First time? Add the @uiarc registry to components.json, or use the full URL:
import { useState } from "react";import { DateRangePicker } from "@/registry/components/date-range-picker/date-range-picker";import type { DateRange } from "@/registry/components/date-range-picker/date-range-picker"; export function ReportRange() { const [range, setRange] = useState<DateRange | null>(null); return ( <DateRangePicker label="Report period" value={range} onChange={setRange} weekStartsOn={1} maxDate={new Date()} /> );}API reference
2 parts. The first is the root.
DateRangePicker
A trigger that grows into a panel with one or two months and a preset rail, then shrinks back with the applied range.
valueDateRange | null–Controlled value, { start, end } as inclusive whole days. Pass null for no selection.defaultValueDateRange | nullnullUncontrolled starting value.onChange(range: DateRange) => void–Called with the range on Apply.labelstring"Date range"Accessible name of the trigger and the dialog.placeholderstring"Select dates"Trigger text with no selection.presetsDateRangePreset[]defaultDateRangePresetsShortcuts: { label, range: (today) => DateRange }. Defaults to Today through Year to date.minDateDate–Earliest selectable day.maxDateDate–Latest selectable day.weekStartsOn0 | 100 is Sunday, 1 is Monday.localestring"en-US"Locale for Intl.DateTimeFormat labels and month titles.months"auto" | 1 | 2"auto"Force one or two months. auto shows two when the boundary is at least 712px wide.boundary() => HTMLElement | null–Element the panel should stay inside. Defaults to the viewport.classNamestring–Extra class on the root.useToday
Hook returning the viewer's local date, or undefined during server render and hydration. Updates at local midnight and when the tab becomes visible.
No props.
- EnterorSpace
- Opens the panel from the trigger; picks a start or end day in the grid.
- Arrow keys
- Move by day or week in the grid, extending the range preview once a start is picked.
- HomeorEnd
- Jump to the start or end of the week.
- PageUporPageDown
- Move by month; add Shift to move by year.
- ArrowUporArrowDown
- Move between presets in the rail; ArrowLeft and ArrowRight in the compact row.
- Escape
- Closes without applying and returns focus to the trigger.
- The trigger has aria-haspopup="dialog", aria-expanded, and a label that includes the current range.
- Each month is a role="grid" labelled by its title, with columnheaders and gridcells using aria-selected for days in range and aria-current="date" for today.
- Days have full date labels and a roving tabindex; months that are leaving turn inert so focus only finds the current set.
- A polite live region announces the start day, then the range and its length.
- The trigger surface grows into the panel on a physical spring and shrinks back on Apply while the formatted label flies into the trigger with a shared layoutId.
- The range highlight is one bar per week that stretches as you hover, and the two ends glide between days.
- Months slide in the direction of travel, and label words roll up or down with time's direction.
- Reduced motion jumps the surface size, drops the shared layout and slide, and uses short opacity fades.
- months="auto" shows two months beside a vertical preset rail when the boundary is at least 712px wide, otherwise one month under a scrolling preset row.
- In compact mode the panel width is min(352px, available) and day cells size between 32 and 42px to fit.
- The panel shifts horizontally to stay 8px inside the boundary, and recomputes on window resize.
- ResizeObservers on the trigger and panel feed the size springs; updates are batched through a microtask.
- Each week's highlight and each end is a motion value animation, so hovering does not re-render the grid beyond the range change.
- Intl.DateTimeFormat instances are memoized per locale.
Notes for AI
Give your coding assistant the Markdown reference instead of screenshots.
- Use when a start and end date are chosen together: report filters, analytics periods, bookings.
- Selection is draft until Apply; onChange fires only on Apply. Cancel, Escape, or an outside click discards the draft.
- Pass custom presets as { label, range: today => ({ start, end }) }; today is the viewer's local date.
- The trigger is disabled until hydration because today is unknown on the server.
- For a single date use date-picker; for an always-visible month use calendar.
The full library index for assistants is at /llms.txt.