Skip to content

DatePicker

Three related components for date input — a segmented field, a field with a calendar popover, and a standalone calendar grid. Built on @internationalized/date (the value layer) and Base UI (Popover), with the segmented input and calendar grid implemented to the ARIA Authoring Practices date-picker/grid patterns. They integrate automatically with AppShell's locale and timezone context.

Implementation note. This is the @internationalized/date + Base UI variant. The public API and accessibility contract are identical to the react-aria variant; only the internals differ. See docs/proposals/date-picker-impl-comparison.md.

Import

tsx
import {
  DateField,
  DatePicker,
  Calendar,
  // Date value helpers (re-exported from @internationalized/date)
  today,
  parseDate,
  getLocalTimeZone,
  type CalendarDate,
  type DateValue,
} from "@tailor-platform/app-shell";

No separate @internationalized/date install needed — the value types and helpers are re-exported from @tailor-platform/app-shell.

DateField

A segmented input that lets users type dates digit-by-digit, with per-segment Up/Down, type-to-fill auto-advance, and full keyboard support.

tsx
<DateField label="Invoice date" />

With description and error

tsx
<DateField
  label="Start date"
  description="Format follows your locale"
  errorMessage="A start date is required"
/>

Controlled

tsx
const [date, setDate] = useState<CalendarDate | null>(null);
<DateField label="Invoice date" value={date} onChange={setDate} />;

DatePicker

A DateField with a calendar popover.

tsx
<DatePicker label="Ship date" />

Constrained + unavailable dates

tsx
<DatePicker
  label="Delivery date"
  minValue={today(getLocalTimeZone())}
  isDateUnavailable={(date) => {
    const dow = date.toDate(getLocalTimeZone()).getDay();
    return dow === 0 || dow === 6; // weekends
  }}
/>

Week start

tsx
<DatePicker label="Date" firstDayOfWeek="mon" />

Calendar

A standalone calendar grid for custom date-selection UIs (e.g. reporting filters).

tsx
<Calendar aria-label="Select date" onChange={(date) => console.log(date)} />

Localization

Locale and timezone come from AppShell automatically. Override per field with locale / timeZone:

tsx
<DatePicker label="Date" locale="ja-JP" />

Segment order, first-day-of-week, and month/weekday names all follow the resolved locale.

Keyboard

  • Segments: / increment/decrement, digits type-to-fill (auto-advance), / move between segments, Backspace clears.
  • Calendar grid: arrows move by day/week, Home/End to week start/end, PageUp/PageDown by month, Shift+PageUp/PageDown by year, Enter/Space selects.

Accessibility

  • The segmented field is a labelled role="group" of role="spinbutton" segments with aria-valuemin/max/now/text.
  • The calendar is a role="grid"; each day is a button with a full-date aria-label; disabled/unavailable days are announced via aria-disabled.
  • The popover is a labelled role="dialog".

Known limitations (this variant). The segments are <div role="spinbutton"> that aren't contentEditable, so a touch device's on-screen keyboard doesn't open for typing — on mobile, use the calendar popover to pick a date (desktop keyboard entry and the calendar both work fully). The APG patterns are implemented and unit-tested but not yet screen-reader-audited, and RTL arrow-key flipping isn't handled. See the implementation comparison ("Known gaps vs react-aria") for the full list.

Props

The tables below list props this variant actually implements for v1 (date granularity). A few props are part of the type surface — kept identical to the react-aria variant so a later swap is source-compatible — but aren't acted on yet; those are called out under Proposed / not yet implemented.

DateFieldProps

PropTypeDescription
labelLocalizedStringField label
descriptionLocalizedStringHelper text below the field
errorMessageLocalizedStringError text; also sets the invalid state
value / defaultValueDateValue | nullControlled / uncontrolled value (CalendarDate at date granularity)
onChange(v: DateValue | null) => voidFires on a complete, valid value; null when cleared
isDisabled / isReadOnly / isInvalidbooleanState flags
isRequiredbooleanSets aria-required on the segments (no visual required indicator yet)
placeholderValueDateValueSeeds unset segments (increment start + segment order)
autoFocusbooleanFocus the first segment on mount
localestringBCP-47 locale override (defaults to the AppShell formatting locale)
namestringEmits a hidden <input> with the ISO value for form submission
aria-labelstringAccessible name when there's no visible label (e.g. compact filters)
classNamestringRoot element class

DateField has no calendar, so minValue / maxValue / isDateUnavailable don't apply to it — they're honoured by DatePicker and Calendar below.

DatePickerProps

All DateFieldProps, plus:

PropTypeDescription
minValue / maxValueDateValueEarliest / latest selectable date in the calendar
isDateUnavailable(date: DateValue) => booleanMark individual dates unselectable (still keyboard-navigable)
firstDayOfWeek"sun" | "mon" | "tue" | "wed" | "thu" | "fri" | "sat"Force the calendar's first column; omit to follow the locale
timeZonestringIANA timezone for resolving "today"; defaults to AppShell timeZone

CalendarProps

The standalone calendar grid. It has no segmented input, so its surface is listed in full:

PropTypeDescription
value / defaultValueDateValue | nullControlled / uncontrolled selected date
onChange(v: DateValue) => voidFires when a date is selected
minValue / maxValueDateValueEarliest / latest selectable date
isDateUnavailable(date: DateValue) => booleanMark individual dates unselectable (still keyboard-navigable)
focusedValue / defaultFocusedValueDateValueControlled / initial focused (visible) date
onFocusChange(date: CalendarDate) => voidFires when the focused date changes (arrows, month paging)
firstDayOfWeek"sun" | "mon" | …Force the first column; omit to follow the locale
isDisabled / isReadOnlybooleanDisable the grid / prevent selection changes
timeZonestringIANA timezone for "today"; defaults to AppShell timeZone
localestringBCP-47 locale override
aria-label / aria-labelledbystringAccessible name for the grid
classNamestringRoot element class

Proposed / not yet implemented

Accepted by the prop types (for parity with the react-aria variant) but not acted on in this variant yet:

PropTypeStatus
granularity"day" | "hour" | "minute" | "second"Only "day" is supported (the default). Time granularities — and the CalendarDateTime / ZonedDateTime values they produce — are the tracked DateTime fast-follow; the calendar has no time selection yet.
hourCycle12 | 24No effect until time granularity lands (12h/24h only matters with an hour segment).
hideTimeZonebooleanUnused; only relevant to ZonedDateTime display (time granularity).

See the proposal's Post-v1 fast-follows for the DateTime plan.

  • Form — wrap date fields with validation
  • Input — plain text input