Date Picker
Pick a single date or a date range from a calendar popover — token-styled, keyboard-navigable, with optional quick presets.
- Status
- Since
0.1.0- Accessibility pattern
- APG date picker dialog
Last updated
Install
Add Date Picker from the VegaStack registry. The CLI verifies the item's integrity hash before writing it.
pnpm dlx shadcn@latest add @vegastack/date-pickerThe same command installs the registry items it composes: @vegastack/popover, @vegastack/button.
It also adds the sanctioned engine to your package.json: react-day-picker (calendar interaction/rendering engine).
Usage
import { DatePicker } from "@/components/ui/date-picker";
function Example() {
const [date, setDate] = React.useState<Date>();
return <DatePicker value={date} onValueChange={setDate} />;
}Anatomy
Date Picker is a compound component. Every exported part, with the
data-slot it renders (generated from the canonical source):
Examples
Anatomy
The Date Picker module exports three building blocks. DatePicker and DateRangePicker compose a
Calendar inside a Popover; use Calendar directly when you want an inline grid.
// Single date — Button-triggered Popover hosting a Calendar.
<DatePicker
value={date}
onValueChange={setDate}
presets={defaultDatePresets()}
calendarProps={{ timeZone: 'UTC', captionLayout: 'dropdown' }}
/>
// Range — two-month Calendar, closes once both ends are chosen.
<DateRangePicker value={range} onValueChange={setRange} />
// Inline — the styled DayPicker with no popover.
<Calendar mode="single" selected={date} onSelect={setDate} />Calendar— a token-styledreact-day-pickerDayPicker(data-slot="calendar"). Forwards every DayPicker prop (mode,selected,onSelect,defaultMonth,numberOfMonths,disabled, …); overrides the nav chevrons (lucide) and the day button.CalendarDayButton— the per-dayButton(data-slot="calendar-day"). Paints selection/range/today state fromdata-*attributes and auto-focuses on keyboard navigation.DatePicker— single date. An outlineButton(data-slot="date-picker-trigger") shows the formatted date and opens the calendar in aPopover(data-slot="date-picker-content"). PasscalendarPropsfor DayPicker knobs such astimeZone,locale,footer,captionLayout,startMonth,endMonth,labels, andformatters.DateRangePicker— a{ from, to }range, same shape, across two months (data-slot="date-range-picker-trigger"/…-content). It also acceptscalendarProps; top-level selection props remain owned by the wrapper.defaultDatePresets()/defaultRangePresets()— ready-made quick-select rails (Today / Tomorrow, Last 7 / 30 days). Pass your ownpresetsto override.
Date and range selection
With presets
A left rail of quick-select buttons (Today / Tomorrow) sits beside the calendar. Selecting a preset closes the popover.
Date range
Pick a start and end across two months. The popover stays open until both ends are chosen; presets cover common windows.
Inline calendar
The bare Calendar with no popover — drop it into a card or sidebar.
Disabled dates
Pass disabledDates (a single Matcher, or an array) to block days in the grid and gate presets. A blocked day renders at reduced opacity and can't be selected; a preset whose date — or, for ranges, any day it spans — falls inside the blocked set is rendered inert and never emits, using the same react-day-picker matcher the grid applies (so the preset gate can't drift from the grid).
Disabled
Set disabled to make the whole control inert — the trigger reads dimmed and won't open the popover.
Dropdown caption
Forward DayPicker knobs through calendarProps. Here captionLayout="dropdown" (bounded by startMonth / endMonth) swaps the static month/year heading for navigable dropdowns — handy for jumping across years.
<DatePicker
value={date}
onValueChange={setDate}
calendarProps={{
captionLayout: "dropdown",
startMonth: new Date(2024, 0),
endMonth: new Date(2027, 11),
}}
/>Single-month range
DateRangePicker shows two months by default; pass numberOfMonths={1} for a compact single-month grid that fits tight layouts.
Custom formatting & locale
The trigger label is formatted with native Intl.DateTimeFormat — no date library. The
default is the readable Jun 24, 2026 ({ year: 'numeric', month: 'short', day: 'numeric' }),
never an ISO string and never the browser's date-input format. Pass formatOptions for any
other shape and locale for a BCP-47 locale.
Prerendering a formatted date needs an explicit locale. With no
locale, the label follows whichever locale the runtime resolves — which is
the build machine on the server and the visitor's browser on the client. When
those differ (Jun 24, 2026 against 24 Jun 2026) React reports a hydration
mismatch and re-renders the tree. Pass locale on any picker that renders a
value in server-rendered or statically exported markup; every example on this
page does.
Use DatePicker wherever you'd reach for <input type="date">. The native input summons the
OS picker, which ignores the design tokens and differs per browser and platform; this
component is themed, keyboard-accessible, and formats consistently everywhere.
API Reference
DatePicker
| Prop | Type | Default | Description |
|---|---|---|---|
align | Align | "start" | Popover alignment relative to the trigger. |
aria-label | string | — | Accessible name for the trigger (recommended when there is no visible label). |
calendarProps | Omit<CalendarProps, "disabled" | "mode" | "onSelect" | "selected"> | — | Props forwarded to the inner Calendar for DayPicker features such as
timeZone, locale, footer, captionLayout, startMonth,
endMonth, labels, and formatters. Selection ownership stays with
DatePicker, so mode, selected, onSelect, and disabled are not
accepted here. |
className | string | — | Extra classes for the trigger button. |
disabled | boolean | — | Disable the whole control. |
disabledDates | Matcher[] | Matcher | — | Dates to disable, forwarded to the calendar's disabled matcher. |
formatOptions | Intl.DateTimeFormatOptions | { year: 'numeric', month: 'short', day: 'numeric' } | Intl.DateTimeFormat options for the trigger label. |
locale | string | — | BCP-47 locale for formatting (defaults to the runtime locale). |
onValueChange | ((date: Date | undefined) => void) | — | Fires with the new date (or undefined when cleared) on selection. |
placeholder | string | "Pick a date" | Trigger text shown when no date is selected. |
presets | DatePreset[] | — | Quick-select presets shown in a left rail. Omit for no presets. |
side | Side | "bottom" | Popover side relative to the trigger. |
value | Date | — | The selected date (controlled). |
Data attributes and CSS variables on DatePicker
| Attribute | Values |
|---|---|
data-empty | "" |
data-slot | "date-picker-content" | "date-picker-trigger" |
DateRangePicker
| Prop | Type | Default | Description |
|---|---|---|---|
align | Align | "start" | Popover alignment relative to the trigger. |
aria-label | string | — | Accessible name for the trigger (recommended when there is no visible label). |
calendarProps | Omit<CalendarProps, "disabled" | "mode" | "onSelect" | "selected"> | — | Props forwarded to the inner Calendar for DayPicker features such as
timeZone, locale, footer, captionLayout, startMonth,
endMonth, labels, and formatters. Selection ownership stays with
DateRangePicker, so mode, selected, onSelect, and disabled are
not accepted here. |
className | string | — | Extra classes for the trigger button. |
disabled | boolean | — | Disable the whole control. |
disabledDates | Matcher[] | Matcher | — | Dates to disable, forwarded to the calendar's disabled matcher. |
formatOptions | Intl.DateTimeFormatOptions | { year: 'numeric', month: 'short', day: 'numeric' } | Intl.DateTimeFormat options for each end of the trigger label. |
locale | string | — | BCP-47 locale for formatting (defaults to the runtime locale). |
numberOfMonths | number | 2 | Number of month grids to show side by side. |
onValueChange | ((range: DateRange | undefined) => void) | — | Fires with the new range (or undefined when cleared) on selection. |
placeholder | string | "Pick a date range" | Trigger text shown when no range is selected. |
presets | DateRangePreset[] | — | Quick-select presets shown in a left rail. Omit for no presets. |
side | Side | "bottom" | Popover side relative to the trigger. |
value | DateRange | — | The selected range (controlled). |
Data attributes and CSS variables on DateRangePicker
| Attribute | Values |
|---|---|
data-empty | "" |
data-slot | "date-range-picker-content" | "date-range-picker-trigger" |
Calendar
| Prop | Type | Default | Description |
|---|---|---|---|
ref | React.Ref<HTMLDivElement> | — | Ref to the calendar root element (data-slot="calendar"). DayPicker itself doesn't forward a
consumer ref, so we wire it onto the overridden Root host alongside react-day-picker's own
animation rootRef. |
showOutsideDays | boolean | true | Show the outside days (days falling in the next or the previous month). **Note:** when a broadcastCalendar is set, this prop defaults to true. Render days from the adjacent months to fill the leading/trailing week rows. |
Data attributes and CSS variables on Calendar
| Attribute | Values |
|---|---|
data-slot | "calendar" |
CalendarDayButton
CalendarDayButton adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).
Data attributes and CSS variables on CalendarDayButton
| Attribute | Values |
|---|---|
data-day | mirrors a prop or state value |
data-range-end | "" |
data-range-middle | "" |
data-range-start | "" |
data-selected-single | "" |
data-slot | "calendar-day" |
data-today | "" |
DatePreset
The shape of each entry in DatePicker's presets array.
DatePreset
| Prop | Type | Default | Description |
|---|---|---|---|
date* | Date | — | The date this preset selects. |
label* | string | — | Button text, e.g. "Today". |
DateRangePreset
The shape of each entry in DateRangePicker's presets array.
DateRangePreset
| Prop | Type | Default | Description |
|---|---|---|---|
label* | string | — | Button text, e.g. "Last 7 days". |
range* | DateRange | — | The { from, to } range this preset selects. |
Preset helpers
Ready-made preset rails. Pass your own presets to override either.
| Prop | Type | Default | Description |
|---|---|---|---|
defaultDatePresets | (now?: Date) => DatePreset[] | — | Today / Tomorrow presets for a single DatePicker. Accepts an optional reference date (defaults to now). |
defaultRangePresets | (now?: Date) => DateRangePreset[] | — | Today / Last 7 days / Last 30 days presets for a DateRangePicker. Accepts an optional reference date (defaults to now). |
DateRange & Matcher
Re-exported from react-day-picker. DateRange is the shape of a range value; Matcher is what disabledDates accepts (a single matcher or an array).
| Prop | Type | Default | Description |
|---|---|---|---|
DateRange | { from: Date | undefined; to?: Date | undefined } | — | A selected range. Used by DateRangePicker's value / onValueChange and DateRangePreset.range. |
Matcher | Date | Date[] | DateRange | DayOfWeek | DateBefore | DateAfter | DateInterval | ((date: Date) => boolean) | — | A react-day-picker day predicate. Passed (singly or as an array) to disabledDates to block days. |
Accessibility
- Built on
react-day-picker, which renders the month as agridofgridcellday buttons with descriptivearia-labels ("Saturday, June 21st, 2026"). - The selected day, range ends, today, and disabled days are all distinguishable by state — not by color alone — via
data-*driven surfaces (bg-primary,ring,bg-accent, reduced opacity). - Opening the picker moves focus into the calendar; arrow keys move day-to-day, and selection restores focus to the trigger. Pressing Esc closes the popover.
- Focus is always visible: the outline
Buttontrigger brightens its border on:focus-visible(focus-visible:border-ring/(--alpha-tint-border)), and the day grid manages roving focus itself (react-day-picker focuses the active day on keyboard navigation). Today's cell additionally carries a neutralring(ring-ring/(--alpha-outline-soft)). - The trigger has no visible text label when empty beyond its placeholder, so pass
aria-label(or wrap it in aField) to name it for assistive tech.
| Key | Action |
|---|---|
| Enter / Space | Open the picker (on the trigger) or select the focused day (in the grid). |
| ↑ ↓ ← → | Move focus between days (±1 week / ±1 day). |
| PageUp / PageDown | Move to the previous / next month. |
| Home / End | Move to the first / last day of the week. |
| Esc | Close the popover and return focus to the trigger. |
| Contract | States tested |
|---|---|
| Behaviour | default, complete, disabled, empty, open, range, selected |
| Accessibility | disabled, labeled, selected, semantic-html |
| Visual | default, hover, disabled, selected, empty |
Do / Don't
Region Select
A searchable combobox of states/provinces for a country — Combobox-powered filtering, with a free-text fallback for countries with no subdivisions.
Color Picker
A swatch-triggered popover that presents a grid of preset colors — pick one, fire onValueChange, mark the selection with a check.