accessibility

What a pointer gets, the keyboard and a screen reader get too. Here is what the calendar does, and what's left for you.

names and announcements

the calendar<div role="group">Its aria-label (labels.calendar by default) followed by the selection: "Calendar, selected date: Wednesday, October 28, 2026". A range and several days are read the same way.
the titlearia-live="polite"Announces each month and view as it changes, and names the grid.
the days<table role="grid">Each day button is named with its full date. Today has aria-current="date"; the cell of a selected day, or of a day in the range, has aria-selected.
the month buttons<button>Named "go to previous month", "go to next year"… as the view shows. At min and max they get aria-disabled and stay focusable, so the focus doesn't drop.
the days ruled outdisabledTheir cells have aria-disabled, and the keyboard skips them.

The texts come from labels: translate them along with the locale.

keyboard and focus

  • The grid is a single Tab stop, even with several months. Tab enters on the selected day, or on the first one that can be chosen.
  • Arrows, Home and End, PageUp and PageDown, Enter and Space: the whole table is in the API. In right-to-left layouts the arrows follow the reading direction.
  • After a month button, the focus stays on it. From the months and the years, Escape goes back to the days with the focus on the title, and a popover around stays open.
  • The calendar never takes the focus on its own. With autoFocus it does, every time it shows up: for a popover or a dialog.

motion and contrast

  • Nothing moves by itself. The colors fade in 150 ms when the theme changes, and not at all with prefers-reduced-motion; the animations guide keeps yours inside it too.
  • The weekday names and the week numbers reach 4.7:1 on the light theme and 5.3:1 on the dark one, over the 4.5:1 of WCAG AA.
  • The days that can't be chosen are faded and struck through: not only a color tells them apart.

your part

  • Name the calendar after what it's for, through containerProps: { 'aria-label': 'Due date' }.
  • In a date picker, the trigger needs aria-haspopup="dialog" and aria-expanded, and the focus goes back to it when the popup closes: the recipes do it.
  • With unstyled or the headless parts, the attributes stay, but the focus ring and the contrast are yours.

The library checks the names, the selection and the accessibility tree with end-to-end tests in Chromium and WebKit, along with the keyboard and the focus.