Date Picker
A date picker built on the native Temporal API — an editable input, a month panel and pointer-anchored popover in one shipped component
Installation
Usage
Basic Usage
The input is fully editable, and validation runs on Enter and when the panel closes:
- separators are flexible — space,
-,/and,all work, and accepted input is auto-formatted to the canonicalYYYY-MM-DDform (2026/8/5becomes2026-08-05); - a complete date commits — the value fills the input and the panel closes;
- a bare year (
2026) opens that year's month panel, and a year-month (2026-08) opens that month's day grid — each picked part fills the input progressively (2026→2026-08→ commit on the day pick); - anything invalid (e.g. letters, an out-of-range month) clears the input and shows the danger border until you type again.
Typing a complete date also previews the selection and the visible month live, before validation runs.
Selected: -
"use client";import { useState } from "react";import { DatePicker } from "@/ui";export default function DatePickerBasicDemo() { const [date, setDate] = useState<Temporal.PlainDate | null>(null); return ( <div className="flex flex-col items-center gap-3"> <DatePicker placeholder="Select or type a date" firstDayOfWeek={1} onValueChange={setDate} /> <p className="text-sm text-muted-foreground"> Selected: {date?.toString() ?? "-"} </p> </div> );}Notes
- Requires the native Temporal API (Chrome 136+, Firefox 139+, Safari 26). The site shows a banner when the current browser lacks it.
- Built on the Picker and Calendar components — assemble those directly when you need full control (see the DatePicker Assembly guide).
API Reference
High-level Components
DatePicker
A controlled-or-uncontrolled date field. The visible month and panel open state are managed internally; the value can be controlled via value / onValueChange.
| Prop | Type | Default | Description |
|---|---|---|---|
value | Temporal.PlainDate | null | - | Controlled selected date |
defaultValue | Temporal.PlainDate | null | - | Initial selected date (uncontrolled) |
onValueChange | (date: Temporal.PlainDate) => void | - | Fired when a date is committed — picked from the panel, or Enter on a complete typed date |
disabled | boolean | - | Disables the input and the panel |
invalid | boolean | - | External invalid state — shows the danger border (also set internally when validation clears the input) |
firstDayOfWeek | 0 | 1 | 0 | First day of the calendar week (0 = Sunday, 1 = Monday) |
isDateDisabled | (date: Temporal.PlainDate) => boolean | - | Predicate disabling calendar dates |
trailing | React.ReactNode | calendar icon | Trailing decoration inside the input |
onKeyDown | React.KeyboardEventHandler<HTMLInputElement> | - | Runs before the built-in Enter commit; preventDefault() cancels it |
classNames | { input?; trailing?; panel? } — each ClassNameValue | - | Custom classes for the input, its trailing decoration and the calendar panel |
styles | { input?; trailing? } — each React.CSSProperties | - | Inline styles for the input and trailing decoration |
Typing behavior: every keystroke mirrors into the input, and a complete date previews the selection and the visible month live. Space, -, / and , are accepted as separators and normalized to YYYY-MM-DD on validation. Validation runs on Enter and when the panel closes — a complete date commits; a bare year opens the month panel and a year-month opens the day grid so the missing part can be picked; anything invalid clears the input.
Keyboard: while the panel is open, ArrowDown moves focus into the calendar's first control and ArrowUp into its last (via usePanelFocus) — typing and calendar navigation never compete for the input. Escape closes the panel and hands focus back to the input.