DatePicker
A date field you can both type into and pick from a calendar — format-driven, with a native Date value. Ships a DateRangePicker sibling for ranges.
@garn/ui/date-pickerDefault
A default date field (type or pick).
Format
A custom `format` (dd.MM.yyyy) driving display + parse.
Constraints
minDate + isDateUnavailable (weekdays this year).
Validation
A required field that clears its error once set.
A date is required.
Presets
Quick-pick shortcuts beside the calendar.
States
Disabled and invalid.
Range
DateRangePicker — a start/end range.
Range Presets
DateRangePicker with preset ranges + dual-month.
Installation
garn is copy-in — the garn CLI writes this component's source into your project, so you own and can edit it. It resolves any base files and installs the npm dependencies below for you.
npx garn-ui add date-picker- Registry
date-picker- Source
- date-picker.tsx
- Deps
date-fnsreact-day-pickerclass-variance-authoritylucide-react- Registry deps
utilsdate-iofield-surfaceuse-controllable-stateuse-merged-refcalendarpopoverbutton
Import
import { DatePicker } from "@garn/ui/date-picker";Props
Generated from the component's TypeScript types.
appearance- Description
- No description.
- Type
"ghost" | "outline" | "soft"- Default
outline
size- Description
- No description.
- Type
"lg" | "md" | "sm" | "xl" | "xs"- Default
md
"aria-invalid"- Description
- No description.
- Type
"false" | "true" | boolean
"aria-label"- Description
- No description.
- Type
string
"aria-labelledby"- Description
- No description.
- Type
string
className- Description
- No description.
- Type
string
clearable- Description
- Show a clear ✕ when a date is set. Default true.
- Type
boolean
clearLabel- Description
- No description.
- Type
string
contentClassName- Description
- No description.
- Type
string
defaultOpen- Description
- No description.
- Type
boolean
defaultValue- Description
- No description.
- Type
Date
disabled- Description
- No description.
- Type
boolean
format- Description
- date-fns token string driving display, parse, and the placeholder.
- Type
string
id- Description
- No description.
- Type
string
isDateUnavailable- Description
- Per-date disabling (holidays, blackout dates).
- Type
(date: Date) => boolean
maxDate- Description
- No description.
- Type
Date
minDate- Description
- Lower / upper selectable bound (inclusive, day granularity).
- Type
Date
name- Description
- Name for a hidden input mirroring the value on form submit as a plain `yyyy-MM-dd` calendar date (the `<input type="date">` wire format — zoneless, never UTC-shifted).
- Type
string
onOpenChange- Description
- No description.
- Type
(open: boolean) => void
onValueChange- Description
- No description.
- Type
(date: Date | undefined) => void
open- Description
- Popover open state (controlled).
- Type
boolean
placeholder- Description
- Field placeholder; defaults to the `format` pattern.
- Type
string
presets- Description
- Quick-pick shortcuts rendered beside the calendar (e.g. Today, Yesterday).
- Type
DatePreset[]
readOnly- Description
- No description.
- Type
boolean
ref- Description
- No description.
- Type
React.Ref<HTMLInputElement>
required- Description
- No description.
- Type
boolean
triggerLabel- Description
- Accessible name for the calendar toggle button.
- Type
string
value- Description
- Selected date (controlled).
- Type
Date
Plus 3 inherited native <input> attributes.
DateRangePicker
"aria-invalid"- Description
- No description.
- Type
"false" | "true" | boolean
"aria-label"- Description
- No description.
- Type
string
"aria-labelledby"- Description
- No description.
- Type
string
className- Description
- No description.
- Type
string
clearable- Description
- No description.
- Type
boolean
clearLabel- Description
- No description.
- Type
string
contentClassName- Description
- No description.
- Type
string
defaultOpen- Description
- No description.
- Type
boolean
defaultValue- Description
- No description.
- Type
DateRangeValue
disabled- Description
- No description.
- Type
boolean
format- Description
- date-fns token string applied to both ends + the placeholder.
- Type
string
id- Description
- No description.
- Type
string
isDateUnavailable- Description
- No description.
- Type
(date: Date) => boolean
maxDate- Description
- No description.
- Type
Date
minDate- Description
- No description.
- Type
Date
name- Description
- Base name for hidden inputs mirroring the range on form submit (`<name>-start` / `<name>-end`), each a plain `yyyy-MM-dd` calendar date.
- Type
string
numberOfMonths- Description
- Number of month grids shown side by side. Default 2.
- Type
number
onOpenChange- Description
- No description.
- Type
(open: boolean) => void
onValueChange- Description
- No description.
- Type
(range: DateRangeValue | undefined) => void
open- Description
- No description.
- Type
boolean
placeholder- Description
- No description.
- Type
string
presets- Description
- Quick-pick ranges rendered beside the calendar.
- Type
DateRangePreset[]
readOnly- Description
- No description.
- Type
boolean
ref- Description
- No description.
- Type
React.Ref<HTMLInputElement>
required- Description
- No description.
- Type
boolean
triggerLabel- Description
- No description.
- Type
string
value- Description
- No description.
- Type
DateRangeValue
Plus 5 inherited native HTML attributes.
Styling
Target these data-slots and remap these tokens to restyle without forking the component.
[data-slot="datepicker"][data-slot="datepicker-input"][data-slot="datepicker-affordances"][data-slot="datepicker-clear"][data-slot="datepicker-trigger"][data-slot="datepicker-presets"]| Property | Token | Tier |
|---|---|---|
height | --garn-control-h-md | semantic · air |
paddingInline | --garn-control-px-md | semantic · air |
borderColor | --garn-foreground | semantic |
iconInset | --garn-control-icon-inset | semantic |
Private instance vars (never externalize): --in-px
Overview
DatePicker is the type-and-pick date field: a text input you can type a date into and a calendar you can pick from, on garn's shared field surface (the same appearance × size grid as Input and Select). A single format prop — date-fns tokens like dd.MM.yyyy or MMM d, yyyy — drives the display, the parse, and the placeholder, so the control round-trips in whatever format you set while the committed value stays a native Date (onValueChange always hands back a Date). Typing parses on blur/Enter and reverts to the last valid value on garbage; picking a day fills the field.
DateRangePicker is the sibling export for a { start, end } range — a dual-month calendar with optional preset shortcuts ("Last 7 days", "This month"). Both share one field + popover core with the Calendar and Popover components, so they can't drift from the rest of the form family. Reach for the bare Calendar instead when the month should always be visible inline; reach for Input when the value isn't a date.
Guidelines
Pair every picker with a Label, and let format document the shape. A placeholder shows the expected pattern, not the field's name — associate a real Label (or Form's wiring). Pick a format that matches your users' locale expectations; it drives typing and display, so they stay in sync.
Constrain to valid dates. Use minDate/maxDate and isDateUnavailable to disable out-of-range and blackout days in the calendar and reject them on typed entry — don't accept-then-error. The calendar's navigation is bounded to the same range.
Signal validity through state. Set aria-invalid on error (the field reddens border + ring) and wire the message via aria-describedby (Form does this). Keep the control keyboard-operable: ArrowDown opens, Enter commits a typed value, Escape closes, and the calendar grid is fully navigable.
Wire format: calendar dates are zoneless. With name, the hidden form input posts a plain yyyy-MM-dd string (the <input type="date"> wire format) — DateRangePicker posts <name>-start/<name>-end in the same shape. The value is never UTC-shifted through an ISO instant; if your backend truly wants an instant, convert the Date from onValueChange yourself (lib/date-io holds the family's serializers).
Best practices
- Pair with a label and choose a
formatthat fits the locale; hold the value in state (or usedefaultValue). - Constrain with
minDate/maxDate/isDateUnavailablerather than validating after entry. - Reflect validation with aria-invalid + a described message; use
presetsfor common ranges.
- Don't rely on the format-pattern placeholder as the label.
- Don't hand-set height/padding — the size variant + --garn-control-* tokens own the grid.
- Don't reach for a segmented MM/DD/YYYY field here — this is the free-text + calendar path (a configurable
formatis its advantage).
Content guidelines
- Label the field with what the date is for ("Start date").
- Use a
formatwhose pattern reads as an example ("MM/dd/yyyy"). - Write errors as specific, fixable instructions ("Enter a date on or after today").
Accessibility
- Role
textbox- ARIA APG
- date-picker-dialog
- Focus
- Border darkens on any focus; keyboard focus adds a neutral ring. On open, focus enters the calendar grid; on close it returns to the field.
Enforceable contracts
Give every date picker an accessible label; the format-pattern placeholder is not a label.
when no associated <label htmlFor>, aria-label, or aria-labelledby
Keyboard
| ArrowDown | Open the calendar from the field. |
| Enter | Commit the typed date. |
| Escape | Close the calendar. |
| ArrowLeftArrowRightArrowUpArrowDown | Move by day / week inside the open calendar grid. |
| PageUpPageDown | Previous / next month in the calendar. |
| EnterSpace | Select the focused day (closes the calendar). |
- • Give the field an accessible label — a <label htmlFor>, aria-label, or aria-labelledby; the placeholder (the format pattern) is not a label.
- • The expected format is announced via a visually-hidden aria-describedby hint.
- • Opening the calendar moves focus to the selected/today day; on close, focus returns to the field.
- • The calendar is react-day-picker's labeled grid (roving focus; days expose aria-selected + disabled).
- • Reflect validation with aria-invalid + a described error message.
- Status
- Stable
- Version
v0.3.0- Introduced
v0.2.0
garn doesn't yet record a per-release version history. This reflects the component's current lifecycle state; full changelog entries will appear once the metadata pipeline emits a lifecycle.history field.