Skip to main content

Calendar Accessibility

Best Practices

The calendar must be structured as a data table. Use <caption> for the month and year, and column headers ( <th> ) for the days of the week. This gives assistive technology the same table navigation commands available on any other data table.

Only use the interactive calendar (date buttons) when the user is actually selecting a date. A read-only calendar — one used purely to display a date or range — should not expose its dates as buttons; use plain text, or a link if the date itself is actionable (e.g. "view orders placed on this date").

Date state (today, selected, start of range, in range, end of range, disabled) must not be conveyed by color or border alone. Pair every visual state with a programmatic equivalent: the interactive calendar uses aria-current="date" for today, aria-pressed="true" for a selected date, the native disabled attribute for unavailable dates, and composes only the range text (start, in, end of range) into each date button's aria-label. The read-only calendar appends visually-hidden text alongside the visible day number.

When focused, the previous/next month buttons must have an accessible label and should name the direction and the month being navigated to, such as "Previous month, August 2026" and "Next month, September 2026".

Interaction Design

This section provides detailed instructions for how different input types should navigate and operate the pattern.

Keyboard

For the interactive calendar, date buttons use a roving tabindex . Only one date (the current or most recently focused date) is in the page's TAB sequence at a time; every other date button has tabindex="-1".

With focus on a date, RIGHT-ARROW and LEFT-ARROW keys must move focus one day forward/backward. DOWN-ARROW and UP-ARROW keys must move focus one week forward/backward. Disabled dates must be skipped over, never landed on.

HOME and END keys move focus to the first and last enabled date currently visible. PAGE-UP and PAGE-DOWN keys move focus to the same weekday in the previous/next month; for a navigable calendar this also changes the visible month.

If arrow, Home, End, Page Up, or Page Down navigation would move focus outside the currently visible month(s) of a navigable calendar, the calendar must advance to the adjacent month and move focus to the corresponding date there.

SPACEBAR or ENTER key on a focused date must select that date. When the calendar supports a range, selecting a second date after a first must create a range between them.

The previous/next month buttons, when present, sit in the normal document tab order — outside of the date grid's roving tabindex — and behave like any other icon button .

Screen Reader

The month and year must be announced via the table's <caption> when the calendar is discovered, or when the virtual cursor enters the table.

Each day of the week must be announced as a column header when a screen reader moves between date cells, per standard data table semantics.

For the interactive calendar, a focused date button announces its day number, its selected state (via aria-pressed) and whether it is today (via aria-current), plus any range text ("start of range", "in range", "end of range") composed into the button's aria-label .

For the read-only calendar, the same state text is appended as visually-hidden text alongside the visible day number, rather than via aria-label , since these dates may also be links.

Pointer

Clicking or tapping a date button must select that date. Clicking or tapping a disabled date must have no effect.

Clicking or tapping a previous/next month button must navigate the calendar by one month.

ARIA Reference

AttributeDescription
aria-labelApplied to the previous/next month buttons to announce which month they navigate to. Applied to an interactive date button, in place of its visible day number, when that date is part of a range, to append the range state text (start, in, end of range).
aria-current="date"Applied to today's date button, when interactive.
aria-pressedApplied to an interactive date button to convey its selected state.
tabindexSet to 0 on exactly one interactive date button (the roving tabindex) and -1 on all others.

Further Reading