How to build an accessible Calendar in React
Renders an APG-compliant role="grid" table with role="row"/role="gridcell"; a roving tabindex keeps exactly one day focusable (the focused date) so arrow keys move focus per APG, Home/End jump to the week edges, PageUp/PageDown change month (with Shift for year), today exposes aria-current="date", selection sets data-selected with aria-selected on the cell, and out-of-range or predicate-disabled days get aria-disabled and are skipped by selection
When to use a Calendar
- Showing an always-visible month grid for picking a single date (inline, not a popover)
- As the building block inside a date-picker or date-range-picker popover
When not to use it
- A compact form field where an inline grid wastes space — use DatePicker (trigger + popover)
- Selecting a contiguous date range — use DateRangePicker, which composes two calendars
Keyboard interactions
Role grid, verified at WCAG 2.2-AA.
ArrowLeftArrowRightArrowUpArrowDownHomeEndPageUpPageDownEnterSpace
Common mistakes
Avoid: Comparing dates with local-time getters (getHours/getDate) after constructing them with new Date(y, m, d)
Prefer: All arithmetic and comparisons use UTC getters/Date.UTC so DST and timezone offsets never shift a day
Local-time construction can land a midnight date on the previous day in negative-offset zones, breaking selection and range math
Example
<Calendar defaultValue={new Date(Date.UTC(2024, 5, 15))} />