## Date Picker (S—021)

A popover calendar for single dates or ranges, with min/max, disabled days and full keyboard navigation.

- URL: https://sundaeui.dev/components/date-picker
- Category: Forms & inputs · Art direction: Understated
- Access: Free (no account needed)
- Stack: React 18+, TypeScript, Tailwind CSS; dependencies: none
- Install: `npx shadcn@latest add https://sundaeui.dev/r/date-picker.json`
- Tags: tailwind, form, date-picker, react, input, popover, calendar

A date picker that behaves the way people expect. The trigger opens a calendar popover. Arrow keys move by day and week, Page Up and Page Down move by month (Shift for a year), and Home and End jump to the edges of the week. Enter picks a day and Escape closes. Focus returns to the trigger afterwards.
Range mode previews the span as you hover before you choose the end date, and orders the dates if you pick them backwards. Days outside min and max, or matching isDisabled (weekends in the demo), are struck through and can’t be selected. Today has a dot, and dates are formatted with Intl for any locale.
Use it for bookings, delivery dates and report filters. For birth dates, where people type faster than they click, use three plain inputs instead.

### Usage

```tsx
import { DatePicker, type DateRange } from '@/components/sundae/date-picker'

const [date, setDate] = useState<Date | null>(null)
<DatePicker label="Delivery date" value={date} onChange={(v) => setDate(v as Date | null)} min={new Date()} />

const [range, setRange] = useState<DateRange | null>(null)
<DatePicker mode="range" label="Report range" value={range} onChange={(v) => setRange(v as DateRange | null)} />
```

### Props

- `mode` (`'single' | 'range'`, default `'single'`): Pick one date or a range.
- `value` (`Date | DateRange | null`): Controlled value.
- `onChange` (`(v) => void`): Called with a Date, a { from, to } range, or null when cleared.
- `min / max` (`Date`): Inclusive bounds.
- `isDisabled` (`(d: Date) => boolean`): Disable specific days.
- `locale` (`string`, default `'en-US'`): Intl locale for labels.
- `weekStartsOn` (`0 | 1`, default `1`): Sunday or Monday first.
- `label / placeholder` (`string`): Visible label and empty text.
- `defaultOpen` (`boolean`): Open on mount without stealing focus.

### Accessibility

- Follows the WAI-ARIA date picker dialog pattern: role="grid", with a roving tabindex on days.
- Every day button has a full spoken date, today is marked aria-current="date", and disabled days are real disabled buttons.
- The month heading is a live region, so paging months is announced.
