A booking widget has to work with a keyboard
Calendars are one of the hardest things to make accessible and one of the easiest to get wrong. Notes from rebuilding ours twice.
Our first booking calendar was a grid of divs with click handlers. It looked right, it worked with a mouse, and it was completely unusable with a keyboard or a screen reader. We have rebuilt it twice since.
Here is what we got wrong and what the current version does.
A day is a button, not a div
Obvious in hindsight. A clickable div is invisible to keyboard navigation and announces nothing. Every selectable day is now a real <button>, so it is focusable and operable for free, and an unavailable day is not a button at all — it is a plain element, so it never receives focus and never promises something it cannot do.
Selection state has to be announced
Styling the selected day with a background colour tells a sighted user which day is picked and tells a screen reader user nothing. The selected day now carries aria-pressed, so it is announced as pressed, and the styling hangs off that same attribute rather than a separate class. One source of truth, and they cannot disagree.
The grid is a table, because it is one
We tried a grid of buttons with ARIA roles. It was fragile. A month calendar is genuinely tabular data — days across, weeks down — so it is a <table> with weekday column headers. Screen readers already know how to navigate that, and we stopped fighting them.
The weekday headers show a single letter visually and the full day name to a screen reader, because “M T W T F S S” read aloud is not a weekday.
Colour is never the only signal
Slots come in three states. Available slots are outlined, nearly-full slots are tinted, and taken slots are dashed and disabled. If you removed all colour from the page you could still tell them apart by border style and by the fact that the third kind cannot be focused.
The thing still on our list
Keyboard arrow navigation within the calendar grid — pressing down to move a week rather than tabbing through 31 buttons. It works but tabbing through a month is tedious, and it is the most legitimate complaint we get. It is being worked on and it is honestly overdue.