Accessibility
DatePicker composes react-day-picker's accessible calendar grid with a Prepared-styled MonthCaption and optional Popover hosting. The grid, month / year Selects, and chevrons carry their own semantics. The app owns the visible label on the trigger (when used as popover content), focus handoff between trigger and grid, constraint copy, and announcing the committed value after Apply. Treat accessibility as a contract between what DatePicker ships and what your surface supplies.
react-day-picker. The day grid exposes a role="grid" with a roving tabindex, arrow-key navigation between days, aria-selected on the selected day(s), and disabled styling on constrained days.MonthCaption controls. Previous / next IconButtons have localized titles ("Previous month", "Next month") and disable at minSelectableDate / maxSelectableDate boundaries. Month / year Selects come from @prepared911/ui-core with localized labels.cta is true, Apply and Reset are @prepared911/ui-core Buttons: focusable, activatable by Enter / Space, and part of the Tab order.trigger is set). Popover manages aria-expanded on the trigger, opens on click / Enter / Space, and closes on Escape or outside click.rangeIncludesDate styling; the component does not announce the span: the app surfaces that via summary copy.trigger, the trigger element owns its own accessible name. Pair an icon-only trigger with aria-label or an adjacent visible label.DatePicker does not announce "You selected Mar 3" when Apply fires. Render the committed value as visible text (a filter pill, a summary line) or in a polite live region.disabled, minSelectableDate, or maxSelectableDate skip dates, explain the rule in surrounding copy ("No dates before your shift start"). Disabled-only styling is not a name.MonthCaption controls, into the grid (focus lands on the currently focused day), and out to Apply / Reset when cta is set.Single / DaysAgo mode; sets the next endpoint in Range mode). Also activates MonthCaption chevrons and Apply / Reset.Popover (when trigger is set) and returns focus to the trigger.role="grid"; individual days are role="gridcell" with aria-selected and aria-disabled as appropriate (react-day-picker).@prepared911/ui-core Select renders a Radix-based listbox with aria-expanded on the trigger.IconButton with localized title; title text is the accessible name.Button elements; no extra ARIA required unless the label needs context (e.g. "Apply date filter").When trigger is used, the Popover returns focus to the trigger on Escape or outside click. The grid receives focus programmatically when the popover opens, so keyboard users land inside the calendar rather than at the month Select. The app is responsible for restoring focus to the surrounding surface (a filter button, a form field) after a committed Apply that closes the popover programmatically.
The grid announces the focused day as it moves ("Monday, March 3") but does not announce the committed value after Apply. When the committed value is important to the workflow—a filter pill, a form summary—render it as visible text. Where the value must be announced without a visible echo, pair the picker with a polite live region:
Keep announcements short and non-interrupting. Mode changes inside a DaysAgo flow should likewise be echoed in visible copy ("last 7 days") rather than left to the grid alone.
react-day-picker ships square day cells; Prepared styles them for a minimum tappable size comfortable on mid-sized touch targets. Shrinking the grid below the default breaks that guarantee.MonthCaption can wrap. Use hideMonthYearSelect to drop the selects rather than let them wrap out of the popover.open and focus yourself.DatePicker hands you Date objects; conversions at the boundary are the app's responsibility.hideMonthYearSelect + chevrons only. Makes the caption smaller but requires more clicks to traverse years: document the trade-off in your surface when using it on long historical ranges.