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-sort to 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 --ring token:
: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> with aria-sort
  • Action menus on each row have unique aria-label attributes

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-label or visible text labels
  • Dynamic content changes are announced via aria-live regions
  • 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