Accessibility and keyboard
None of this existed before 1.0. The old Vue component resolved initialFocus
to a // TODO and handled no keys at all, which made the calendar unusable
without a mouse. It is now implemented once in @taqwim/calendar-core and is
therefore identical in every framework.
Try it
Section titled “Try it”Every key you press is logged below. A key shown in teal is one
store.handleKeydown claimed — the adapter called preventDefault() on it.
Anything else fell through to the browser.
Only one cell is tabbable at a time — a roving tabindex — so Tab enters and leaves the grid rather than walking every day of the month. PageUp/PageDown page by month, Shift with them by year.
Keyboard
Section titled “Keyboard”| Key | |
|---|---|
| ← → | Previous / next day |
| ↑ ↓ | Previous / next week |
| Home End | First / last day of the week |
| PageUp PageDown | Previous / next month |
| Shift + PageUp/PageDown | Previous / next year |
| Enter Space | Select the focused day |
Under dir="rtl" the horizontal keys mirror: → moves to the previous
day, because that is the direction the eye travels. This matters for the Arabic
locale the library primarily exists for.
Moving focus past the edge of the visible month pages the calendar, so the focused date is always on screen.
The roving tabindex
Section titled “The roving tabindex”Exactly one cell is in the tab order at a time, so Tab enters and leaves the grid rather than walking forty-two buttons.
Before anything has been focused, that one cell is resolved in the same order
initialFocus uses: the selected date, else today, else the first selectable day
of the visible month. Disabled days are skipped — a minValue in the middle of a
month must not strand keyboard users outside the grid.
What the store emits
Section titled “What the store emits”The adapters do not compute accessibility attributes; the store does, and they spread what it returns.
- Root:
role="application"and anaria-labelnaming the visible month. - Grid:
role="grid", witharia-readonly/aria-disabledwhen applicable. - Cell:
role="gridcell", carryingaria-selected. - Cell trigger:
role="button",aria-labelwith the full localised date, andaria-disabledwhen the day is blocked. It deliberately does not carryaria-selected— that attribute is invalid onrole="button", and the enclosing gridcell already has it. - A visually hidden
role="heading"announces the visible month.
What is checked
Section titled “What is checked”@axe-core/playwright runs against every adapter on every CI run, in English
left-to-right and Arabic right-to-left. It is not advisory: a violation fails the
build.
That check earned its place immediately. It found the invalid aria-selected
above, and two colour-contrast failures in the default theme — the muted
foreground at 2.53:1 and adjacent days at 1.67:1, the latter caused by fading
them with opacity.