Accessibility
Last updated on 2026-08-26
The Booking Kit is built with accessibility as a core requirement. Every screen meets WCAG 2.1 AA standards, with keyboard navigation, screen reader support, and proper focus management throughout.
Foundation: Radix UI
All interactive components are built on Radix UI -- a headless library implementing WAI-ARIA design patterns. This provides proper ARIA attributes, keyboard navigation, and focus management out of the box for dialogs, dropdowns, tabs, tooltips, and select menus.
Skip Navigation
Every page includes a skip-to-main-content link that becomes visible on Tab focus. The SkipLink component is rendered in the root layout:
import { SkipLink } from "@/components/layout/skip-link"
// In app/layout.tsx
<SkipLink />
The skip link uses the .skip-link CSS class defined in globals.css, which positions the link off-screen until focused.
Keyboard Navigation
| Key | Action |
|---|---|
Tab |
Move focus to next focusable element |
Shift+Tab |
Move focus to previous element |
Enter / Space |
Activate buttons, links, toggles |
Escape |
Close dialogs, menus, popovers, sheets |
Arrow Keys |
Navigate calendar dates, time slots, menu items, tabs |
Home / End |
Jump to first/last item in a list or calendar row |
Calendar-Specific Navigation
The calendar grid and slot picker support keyboard navigation:
| Key | Action |
|---|---|
ArrowLeft / ArrowRight |
Previous / next day |
ArrowUp / ArrowDown |
Previous / next week (calendar) or slot (slot picker) |
Enter |
Select the focused date or time slot |
Screen Reader Support
Live Regions
The LiveRegion component (components/a11y/live-region.tsx) announces dynamic updates:
import { LiveRegion } from "@/components/a11y/live-region"
<LiveRegion message="Booking confirmed for August 25 at 2:00 PM" politeness="polite" />
Key areas that announce changes:
- Booking confirmations and cancellations
- Schedule changes and reschedules
- Status updates on booking cards
- Filter results count changes
- Form validation errors
Visually Hidden Text
The VisuallyHidden component provides screen-reader-only labels:
import { VisuallyHidden } from "@/components/a11y/visually-hidden"
<button>
<SearchIcon />
<VisuallyHidden>Search bookings</VisuallyHidden>
</button>
The .sr-only CSS utility class is also available in globals.css for inline use.
ARIA Labels
- Every icon button has an
aria-label(search, export, actions, close) - Sortable table columns use
aria-sortto announce sort direction - Status badges communicate their state to screen readers
- Calendar days include availability information in their accessible names
Focus Management
- Focus trap in all modal dialogs, sheets, and alert dialogs -- focus cannot leave the overlay
- Focus restoration -- when a dialog closes, focus returns to the trigger element
- Visible focus ring on all interactive elements using the
--ringtoken:
:focus-visible {
outline: 2px solid var(--ring);
outline-offset: 2px;
}
- Auto-focus -- dialog forms auto-focus the first input field on open
Semantic HTML
Heading Hierarchy
Every page follows a single h1 with nested h2-h6 headings. No heading levels are skipped.
h1: "Bookings"
h2: "Today's Schedule"
h2: "Upcoming"
h2: "All Bookings" (table section)
Landmarks
Pages use semantic HTML elements for navigation:
| Element | Purpose |
|---|---|
<nav> |
Sidebar navigation, breadcrumbs |
<main> |
Primary page content |
<aside> |
Sidebar panel |
<header> |
App header bar |
<section> |
Grouped content areas |
Data Tables
Booking tables use semantic HTML:
<table>,<thead>,<tbody>,<th>,<td>- Sortable column headers use
<button>witharia-sort - Action menus on each row have unique
aria-labelattributes
Touch Targets
All interactive elements meet the 44px minimum touch target size. The .touch-target CSS class is available for custom elements:
.touch-target {
min-width: 44px;
min-height: 44px;
}
Color Contrast
Design tokens meet WCAG 2.1 AA contrast ratios in both light and dark modes:
- Body text: minimum 4.5:1 contrast ratio
- Large text (18px+ or 14px bold+): minimum 3:1
- Interactive elements: minimum 3:1
- Status badges: success, warning, destructive, and info colors all maintain AA contrast against their backgrounds
The oklch color system makes it easy to verify contrast because lightness is perceptually linear.
Reduced Motion
The globals.css file includes a prefers-reduced-motion media query that disables all animations and transitions:
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
This affects page entrance animations, calendar transitions, slot picker hover effects, stagger animations, and button press micro-interactions.
Accessibility Checklist
When customizing the Booking Kit, verify:
- Every interactive element is reachable via keyboard
- Focus order follows the visual layout
- Color is not the only way to communicate status (icons and text labels supplement badges)
- Custom components include
aria-labelor visible text labels - Dynamic content changes are announced via
aria-liveregions - New pages maintain the heading hierarchy (single
h1, no skipped levels) - Custom colors meet WCAG AA contrast ratios (use a contrast checker)
Next Steps
- Design Tokens -- contrast-safe color system
- Components -- accessibility features per component
- FAQ -- common accessibility questions