Date Picker
A date picker built from the Popover and the Calendar, with range, dropdown captions, typed input and presets.
Overview
The date picker is a composition, not a new primitive: a Popover
holding a Calendar. The kit ships the two
pieces that glue them together, so the field half matches the rest of the
text-field family. DatePickerTrigger is a SelectTrigger in every way that
shows — the same 56px frame whose outline ring carries state (hover, focus, open,
invalid, disabled), a calendar glyph in the trailing slot, and the placeholder in
the muted tone until a date lands. DatePickerContent is the shared popup
surface with the popover's padding and fixed width stripped, so the grid sizes
itself.
Formatting stays with you: the trigger renders whatever label you hand it, so
date-fns, Intl.DateTimeFormat or a hand-rolled string all work.
$ npx morpha-ui add date-pickerExamples
Range
Keep the grid in mode="range" and label the trigger with both endpoints. The
popup stays open while the second endpoint is still missing.
Date of birth
captionLayout="dropdown" turns the month and year captions into selects, and
startMonth/endMonth bound the range — jumping back three decades beats 400
clicks on the previous-month arrow.
Input
When typing is the primary path, drop DatePickerTrigger and render the
PopoverTrigger as an InputGroupButton inside an InputGroup:
the field takes the keystrokes, the button opens the grid. Parse on change with
parse + isValid, and keep the calendar's month in state so it follows what
was typed. ↓ opens the popup from the keyboard.
Date and time
Pair the picker with a native type="time" input inside an InputGroup — the
browser's own control handles the clock half, keeping the platform's locale and
keyboard behaviour, with no second popup.
Presets
Put a Select of shortcuts above the grid inside DatePickerContent: the common
answers become one click, and the calendar stays there for everything else.
Installation
Morpha UI components are distributed via the Morpha CLI: npx morpha-ui add date-picker. It pulls in the calendar and popover items it composes.
npx morpha-ui add date-pickerUsage
import { format } from "date-fns"
import { Calendar } from "@/components/ui/calendar"
import {
DatePicker,
DatePickerContent,
DatePickerTrigger,
} from "@/components/ui/date-picker"const [open, setOpen] = React.useState(false)
const [date, setDate] = React.useState<Date | undefined>()
return (
<DatePicker open={open} onOpenChange={setOpen}>
<DatePickerTrigger>{date && format(date, "PPP")}</DatePickerTrigger>
<DatePickerContent>
<Calendar
mode="single"
selected={date}
onSelect={(value) => {
setDate(value)
setOpen(false)
}}
autoFocus
/>
</DatePickerContent>
</DatePicker>
)Hand the trigger the formatted date as its children; leave it empty and the
placeholder shows instead. Closing on select is your call — a range picker
wants to stay open, a single-date picker does not.
API Reference
DatePicker
Re-export of the Popover root — open,
defaultOpen, onOpenChange, modal.
Prop
Type
DatePickerTrigger
Prop
Type
DatePickerContent
Takes the props of PopoverContent (align,
side, sideOffset, alignOffset), defaulting align to start and dropping
the popover's w-72 p-4.
Accessibility
- The trigger is a real button: Enter / Space opens the popup, Esc closes it and returns focus to the trigger — the Popover primitive handles the focus trip.
- Pass
autoFocusto theCalendarso the grid takes focus on open; from there Arrow keys move by day and Page Up/Down by month. - The trigger carries no visible text of its own when empty — give it a
Label(viaid/htmlFor) or anaria-labelnaming the field. - Icon-only triggers, like the
InputGroupButtonin the input example, need anaria-label(Open calendar). - Errors go through
aria-invalid, which recolors the ring toerror.