Skip to main content

DatePicker

betav0.1.0

Date field for forms and filters: an editable text field wearing the Input shell (same label, required marker, helper and error rows, same hover/focus/disabled/error states), a calendar button in its end slot, and a popover calendar that follows the WAI-ARIA date picker dialog pattern (grid, roving focus, arrows by day and week, PageUp/PageDown by month, Home/End, Escape). `withTime` adds an hours/minutes (optionally seconds and AM/PM) row and an OK button — the date-time field the products use most. The value is a Date instant or null, read and edited in the browser's zone by default or in the zone `timezone` names ('UTC' or an IANA name); typing is parsed against a locale-derived or explicit `format` on every keystroke and reported with a MUI X shaped validation context. Covers MUI X DatePicker and DateTimePicker (and the date-only DateTimePicker usages); the time-only field is TimePicker. Hand-built calendar engine, no date dependency.

Variants

Type a date or pick one from the calendar
A date is required

Examples

<DatePicker label="Start date" value={date} onChange={setDate} />
<DatePicker
  label="Due date"
  value={date}
  onChange={(next, { validationError }) => { if (!validationError) setDate(next); }}
  minDate={new Date()}
  helperText="Today or later"
/>
<DatePicker label="First seen" withTime clearable value={date} onChange={setDate} onAccept={submit} />
// Seconds and a 24-hour clock come from the pattern
<DatePicker label="Stop time" withTime format="yyyy-MM-dd HH:mm:ss" value={date} onChange={setDate} />
<DatePicker label="Report date" locale="fr-FR" disableFuture value={date} onChange={setDate} />
<DatePicker label="Date" error="A date is required" required value={null} onChange={setDate} />
<DatePicker label="Created" disabled value={new Date(2026, 8, 26)} />
<DatePicker aria-label="Filter from" value={from} onChange={setFrom} />

Props

PropType
valueDate | null — Controlled value, an instant read in the browser's zone or in the zone `timezone` names (RULE-02, RULE-08)
defaultValueDate | null — Uncontrolled initial value
onChange(value: Date | null, context: { validationError: null | 'invalidDate' | 'minDate' | 'maxDate' | 'minTime' | 'maxTime' | 'disableFuture' | 'disablePast' }) => void — Fires on every calendar pick, keystroke and clear; unparsable text reports null with 'invalidDate', a date outside the bounds reports the date with the bound (RULE-04)
onAccept(value: Date | null) => void — Fires when the user commits: calendar pick (date-only), OK (withTime), Enter, blur after typing, clear (RULE-05)
withTimeboolean — Adds the hours/minutes row (seconds when the pattern has `ss`, AM/PM when it has `a`) and the OK button; a calendar pick no longer closes the panel (default: false, RULE-03)
formatstring — Pattern with the tokens yyyy MM dd HH hh mm ss a; anything else is a literal, and a quoted run ('T') is a literal whose quotes are not rendered. Defaults to the locale's numeric order and clock (RULE-06)
localestring — BCP 47 tag for month and weekday names, the default pattern, the AM/PM strings and the week start. Defaults to the host locale (RULE-06)
weekStartsOn0 | 1 | 2 | 3 | 4 | 5 | 6 — First day of the week, 0 = Sunday. Defaults to the locale's
minDateDate — Earliest selectable day, inclusive (day precision)
maxDateDate — Latest selectable day, inclusive (day precision)
minDateTimeDate — Earliest selectable instant; the day it falls on stays selectable and an earlier clock reports 'minTime' (MUI X minDateTime)
maxDateTimeDate — Latest selectable instant; a later clock reports 'maxTime' (MUI X maxDateTime)
timezonestring — The zone the calendar and the text are read in: "default" (the browser's), "UTC" or an IANA name such as "Europe/Paris". The value stays an instant; an unknown name warns and falls back to the browser (RULE-08)
disableFutureboolean — Disables every day after today (default: false)
disablePastboolean — Disables every day before today (default: false)
clearableboolean — Shows a clear button next to the calendar button while the field holds a value (default: false, RULE-07)
openboolean — Controlled open state of the calendar panel
defaultOpenboolean — Uncontrolled initial open state (default: false)
onOpenChange(open: boolean) => void — Open-state change callback
labelstring — Visible label above the field. Omit → aria-label or aria-labelledby required (RULE-01)
requiredboolean — Renders the `*` marker (with a label) and sets aria-required (default: false)
infoTooltipReactNode — Info affordance next to the label (caller-owned); renders only with a label
placeholderstring — Placeholder shown when empty. Defaults to the pattern with its date tokens in capitals (DD/MM/YYYY)
helperTextReactNode — Helper text under the field; replaced by `error`
errorstring — Error message. String only (no boolean) — sets aria-invalid and the error tokens (RULE-05)
startIconReactNode — Single leading decorative icon, always aria-hidden
disabledboolean — Disables the field, both buttons and the panel (default: false)
readOnlyboolean — Text not editable, calendar button disabled (default: false)
openCalendarLabelstring — Accessible name of the calendar button (default: "Open calendar")
clearLabelstring — Accessible name of the clear button (default: "Clear date")
calendarLabelstring — Accessible name of the panel (default: "<label> calendar" or "Calendar")
previousMonthLabelstring — (default: "Previous month")
nextMonthLabelstring — (default: "Next month")
monthSelectLabelstring — Accessible name of the month select (default: "Month")
yearSelectLabelstring — Accessible name of the year select (default: "Year")
hoursLabelstring — Label of the hours field (default: "Hours")
minutesLabelstring — Label of the minutes field (default: "Minutes")
secondsLabelstring — Label of the seconds field (default: "Seconds")
periodLabelstring — Accessible name of the AM/PM select (default: "AM/PM")
confirmLabelstring — Label of the OK button shown with withTime (default: "OK")
classNamestring — Additional CSS classes on the outer wrapper

Accessibility

2.1 AAWCAG pass

The field is a native text <input> wearing Input's classes (inputVariants), so its label, required marker, helper/error rows, aria-required, aria-invalid and aria-describedby wiring are Input's; omitting label requires aria-label or aria-labelledby, enforced by a dev-only console.warn like Input (RULE-01). The calendar button is an FDS IconButton acting as the Radix Popover trigger (aria-expanded, aria-haspopup=dialog); the panel is a modal dialog named after the label. Focus moves on open to the active day (the value, else today) as the WAI-ARIA APG date picker dialog prescribes, and Escape returns it to the calendar button. The grid is a <table role=grid> whose caption (month + year) is an aria-live region the grid is labelled by; column headers carry the full weekday name in `abbr`; each day is a focusable gridcell (roving tabindex) with a full spoken date, aria-selected on the chosen day, aria-current=date on today and aria-disabled on a day outside the bounds. Keyboard: arrows move by day and week, Home/End to the week's ends, PageUp/PageDown by month, Shift+PageUp/PageDown by year, Enter/Space select; month and year are also FDS Selects. Today and the selected day are marked by a 2px border in addition to colour, so neither state relies on colour alone (WCAG 1.4.1); an invalid typed value is reported to the consumer, who renders the message through `error` (RULE-05). The hours/minutes/seconds fields are FDS Inputs with visible labels; AM/PM is an FDS Select. Every rendered string is a prop with an English default so products localise. The field's disabled tones follow Input's declared pairs; day cells outside the bounds are aria-disabled and painted with the same disabled text token, declared here because the day stays focusable. Focus uses the standard FDS ring on the input, the buttons, the selects and the day cells.