Calendar

A full month or year surface for displaying and selecting dates, with a cell renderer for putting your own content in a day.

Accessibility

Calendar renders a real table and little else. Keyboard support and state announcements are left to you.

What it renders

The grid is a real <table>, and that is most of what it gives assistive technology.
  • In month mode a header row of seven <th> weekday names comes from the locale. They carry no scope, which one header row usually gets away with.
  • In year mode there is no header row.
  • Each day is a plain <td> with a click handler, with no grid role, no aria-selected and no tabindex.
  • Each cell's full date is only in its title attribute, which shows on hover and does not reach touch users.

The grid is not keyboard operable

The whole grid is one tab stop with no key handling: no arrow keys, no Enter, no Home or End. A day can only be picked with a click.
  • The default header works from the keyboard, so a keyboard user can move from month to month. They cannot land on a day.
  • If choosing a date is the point of the screen, use DatePicker, whose popup has full keyboard support.
  • If the grid has to stay, put the action on a real <button> or link inside the cell through fullCellRender. It is reachable and handles Enter and Space.
  • Do not add tabIndex to every cell. That is 35 tab stops to cross a month.

Selected and today are color only

Neither state reaches assistive technology. A reader who cannot see the color cannot tell which day is selected or which is today.
  • Today's marker is a thin line, so it needs 3:1 contrast against the cell. A custom primary color is the first thing that can lose it.
  • Put the words in the cell: a visually hidden “Today” or “Selected” inside what cellRender returns.
  • Also state the selection outside the grid, in a heading or a live region beside it.

Content you put inside a cell

  • The click handler is on the cell, so a button inside a cell also selects that day. Decide which should happen first.
  • A dot or a colored Badge is a picture. Add text: “3 bookings” beside the dot, or an sr-only span with the same words.
  • The full form's content slot scrolls, and no keyboard can reach a scroll area inside a cell. Show two or three items and a count that links somewhere.
  • Do not put interactive content in the compact form, where anything you add also changes the height of the row.

Naming the grid and its switchers

The calendar names nothing, including itself.
  • aria-label on Calendar is dropped. Wrap the calendar in your own labeled element, or let a heading above it name it.
  • The default header's year and month selects have no label. They announce only a bare year and month.
  • headerRender is the only way to label them. Rebuilding the toolbar is also the chance to add previous and next buttons.
  • The Month and Year switch is labeled already, with the locale's own words.
  • Weekday names, the first day of the week and month names come from the ConfigProvider locale.

Motion

Calendar only animates the cell background and the date color. Turn both off with ThemeScope theme={{ token: { motion: false } }}, not a nested ConfigProvider, which re-themes only part of the subtree.
Anything that moves inside a cell is yours, and belongs behind motion-safe:.