DatePicker
ImplementedNative <input type="date"> wrapper with ISO value normalization — accepts string | Date, emits yyyy-mm-dd strings. The browser owns the calendar popup, keyboard navigation and locale formatting.
patterns/molecules/datepicker.htmlNative `<input type="date">` wrapper for standalone labelled date fields. The browser owns the calendar popup, keyboard navigation and locale rendering; the molecule adds ISO value normalization (`string | Date` in, `yyyy-mm-dd` out), label/error/hint wiring, size presets and combobox semantics (`role="combobox"` + `aria-haspopup="dialog"`).
DatePicker
Input molecule — Form & Choice (Family 3)
Accepts an ISO `yyyy-mm-dd` string or a Date, normalizes to `yyyy-mm-dd` using local parts (no UTC day-shift), and emits ISO strings from `onChange`. The visible calendar popup is the browser UA widget — the DS never renders a custom grid.
Labelled field with value + min/max
min/max are ISO yyyy-mm-dd; the browser renders the value and its calendar in the viewer’s locale.
Sizes — sm (h-8) / md (h-10, default) / lg (h-11)
md is h-10 to match the Input and Select atoms — the DS standard form-control height.
Error state
On-sale date must be before the event date.
Sets aria-invalid and announces the message viaaria-describedby.
Hint state
Times are shown in the venue's local timezone.
Error + hint — error wins (mutual exclusion)
Refund deadline cannot be after the event date.
With both set, only the error renders — the hint paragraph is suppressed, the same error-wins rule FormField documents. Known wiring defect (fix tracked in the component hardening pass): aria-describedby still references the suppressed hint id, and the visible label is not yet programmatically associated (htmlFor). Pass an explicit id per instance until that pass lands.
Usage
Standalone labelled date field — e.g. a sales-filter From/To pair. The value contract is ISO yyyy-mm-dd in and out; pass max (or min) to guard a range against another field.
import { DatePicker } from '@epasslive/design-system'
export function SalesFilter({ fromIso, toIso, setFromIso }: {
fromIso: string
toIso: string
setFromIso: (iso: string) => void
}) {
return (
<DatePicker
id="sales-from"
label="From"
value={fromIso}
onChange={setFromIso}
max={toIso}
/>
)
}DatePicker vs Input type="date"
- Standalone labelled date field (filters, settings, simple forms) → DatePicker: label/error/hint wiring, ISO normalization (accepts
string | Date, emitsyyyy-mm-dd), size presets, combobox semantics. - Dense composite form row already converting values via
toLocalInput/fromLocalInputhelpers →Input type="date"is acceptable (the helpers already speakyyyy-mm-dd). - A bare styled
<input type="date">is always a violation — never introduce a third pattern.
Accessibility
- The input carries
role="combobox"+aria-haspopup="dialog", mirroring the browser’s native date-input exposure; the popup itself is the browser UA widget — keyboard navigation and popup semantics are delegated to it. aria-invalidwhenerroris set; error and hint messages are wired througharia-describedby.- Known wiring defects (documented; fix tracked in the component hardening pass): the visible label lacks
htmlFor; the id fallback is the literal"datepicker"so multiple unlabeled pickers collide; whenerrorsuppresseshint,aria-describedbystill references the missing hint id. Until fixed, pass an explicit uniqueidand always passlabel(orariaLabel).
Props
valuestring | DateControlled value. ISO `yyyy-mm-dd` string or Date — both normalized to `yyyy-mm-dd` using local parts. Invalid dates render empty.
onChange(value: string) => voidChange handler. Always receives the ISO `yyyy-mm-dd` string — never a Date.
minstringMinimum selectable date (ISO `yyyy-mm-dd`). Passed to the native input; the browser blocks earlier dates.
maxstringMaximum selectable date (ISO `yyyy-mm-dd`).
size'sm' | 'md' | 'lg'mdHeight preset: sm h-8, md h-10 (matches Input/Select atoms), lg h-11.
labelstringVisible label above the field. Required-in-practice — without label or ariaLabel the announced name falls back to hardcoded English "Select date".
ariaLabelstringAccessible name when no visible label fits. Takes precedence over label.
errorstringValidation error message. Sets aria-invalid, renders the message in error color, and suppresses the hint.
hintstringHelper text below the field. Suppressed when error is set — error wins (same rule as FormField).
disabledbooleanfalseDisables the field (cursor-not-allowed, 50% opacity).
idstringExplicit input id. Pass a unique one per instance — the error/hint ids derive from it (collision fix pending).
classNamestringClasses appended to the root wrapper (wrapper is w-full).
placeholderstringNo-op on `<input type="date">` in modern browsers — the UA paints its own locale placeholder.
localestringDEPRECATED — ignored. The native input localizes from its ISO value in the viewer’s browser locale. Kept for API compatibility.
Props
DatePicker
valuestring | Date—Selected date; normalized to a yyyy-mm-dd string on change.
onChange(value: string) => void—Emits the ISO yyyy-mm-dd string (uncontrolled when omitted).
disabledbooleanfalseDisabled state
labelstring—Field label. Required in practice — the accessible-name fallback is hardcoded English 'Select date'.
errorstring—Validation error message; suppresses the hint when set.
minstring—Minimum allowed value (ISO 8601 date).
maxstring—Maximum allowed value (ISO 8601 date).
sizeDatePickerSize'md'Visual size preset.
classNamestring—Additional CSS classes on the wrapper.
ariaLabelstringlabel, then 'Select date'Accessible name — defaults to label, then English 'Select date'.
idstring'datepicker'Stable input id; also keys the hint/error description ids.
placeholderstring—No-op in modern browsers — native date inputs ignore placeholder text.
hintstring—Helper text below the field; hidden when error is set.
localestring—Deprecated — ignored. The native input localizes in the browser locale.
<DatePicker />Accessibility
ARIA
1 ruleKeyboard
1 rulevalidation
1 ruleUsage
Import
import { DatePicker } from "@epasslive/design-system"Example 1
<DatePicker label="Event date" value={isoDate} min={todayIso} />Example 2
<DatePicker label="Doors open" size="sm" hint="Local venue time" />Anti-patterns
— Do not pass Date objects to min/max — they are ISO strings here
— Do not expect a custom calendar or time picker — the browser UA widget owns the popup