Date Picker
Single dates and date ranges in a calendar popover.
Preview
Installation
pnpm dlx shadcn@latest add http://127.0.0.1:3017/r/date-picker.jsonInstalls the picker source, Calendar, Popover, and Button with their dependencies. Field is a separate component used in these examples. Keep tw-animate-css imported for popover animations. The endpoint is local; see the registry guide.
Usage
'use client';
import * as React from 'react';
import { DatePicker } from '@/components/ui/date-picker';
import { Field, FieldLabel } from '@/components/ui/field';
export function DueDate() {
const [date, setDate] = React.useState<Date>();
return (
<Field>
<FieldLabel htmlFor="due-date">Due date</FieldLabel>
<DatePicker
id="due-date"
value={date}
onValueChange={setDate}
name="dueDate"
/>
</Field>
);
}Both triggers use an 8px inline-start icon inset and an 8px icon-to-text gap, matching Input Group. They remain buttons that open a calendar.
Both pickers are controlled: store the selection in the parent. Selecting a single date closes the panel and returns focus to the trigger. Provide an external clear action by setting the value to undefined when your workflow allows clearing.
This packages the shadcn Date Picker composition into reusable components using Migoy's existing calendar and popover.
Composition
DatePicker and DateRangePicker package the same Calendar and Popover building blocks:
Field
├── FieldLabel
└── DatePicker or DateRangePicker
└── Popover (internal)
├── PopoverTrigger → Button
└── PopoverContent
├── PopoverTitle (visually hidden)
├── Calendar
└── PopoverClose → Apply range (range picker only)Associate FieldLabel with the picker's id. The parent owns value and onValueChange; each picker manages its own popup visibility. Pass calendar configuration through calendarProps rather than nesting Calendar inside a picker. Optional named hidden inputs are rendered alongside the popover for form submission.
For a custom layout, compose Popover and Calendar directly. Use PopoverTrigger's Base UI render prop with Button, provide a PopoverTitle, and place Calendar inside PopoverContent. Keep selection state in your wrapper and decide when selection should close the popup.
Range picker
Select a start and end date, then apply the range.
import type { DateRange } from 'react-day-picker';
import { DateRangePicker } from '@/components/ui/date-picker';
const [range, setRange] = React.useState<DateRange | undefined>();
<DateRangePicker
aria-label="Reporting period"
value={range}
onValueChange={setRange}
startName="from"
endName="to"
/>Range changes update the parent immediately. Apply range closes the panel once both dates exist; Escape or clicking outside also closes it and retains the current selection. Apply does not submit a form or save to a server. Months stack on narrow screens.
Date of birth
Use calendarProps to configure dropdown navigation, disabled dates, locale, or navigation bounds. Navigation bounds and selectable dates are separate settings.
<DatePicker
aria-label="Date of birth"
value={date}
onValueChange={setDate}
calendarProps={{
captionLayout: 'dropdown',
startMonth: new Date(1920, 0),
endMonth: new Date(2026, 10),
disabled: { after: new Date(2026, 10, 1) },
}}
/>The demos use a fixed November 2026 date for repeatable previews. In a product, derive date limits from your application's current date and timezone.
Disabled
API and forms
| Prop | Purpose |
|---|---|
value, onValueChange | Controlled Date or DateRange; either may be undefined |
placeholder | Empty trigger text and accessible popup title |
id, aria-label, aria-labelledby | Label the trigger |
aria-describedby, aria-invalid | Connect helper text or validation |
disabled | Disable the trigger and omit hidden form values |
calendarProps | DayPicker settings excluding selection ownership |
name | Single-date hidden form field |
startName, endName | Range hidden form fields |
Named values serialize as local date-only yyyy-MM-dd strings; empty selections submit empty strings. Hidden inputs do not enforce native required validation. Validate required dates, complete ranges, and allowed dates in your form logic and again on the server. Reset parent state explicitly when resetting a form. Use local date constructors instead of UTC date-string parsing for date-only values.
Accessibility
- Associate a visible FieldLabel with the trigger's
id, or supply an accessible name. - Enter or Space opens the picker and focuses the calendar.
- Arrow keys move between days; Enter or Space selects a date.
- Escape dismisses the popup and returns focus to the trigger.
- Calendar titles name the popup; disabled days retain DayPicker's accessible state.
See React DayPicker for calendar selection and keyboard behavior.