Accessibility Guide

Keyboard interaction, focus management, ARIA patterns, and testing checklists for building with GHDS.

This is reference documentation for building accessible screens with GHDS — see About & Accessibility for the project’s overall accessibility commitment and how the automated color-contrast validation works.

Keyboard Interaction

Most components (Button, Card, Input, Alert, Badge, Avatar, Spinner, Progress, Skeleton, Table) rely entirely on native browser keyboard handling — no custom onKeyDown or tabIndex logic. Overlay components add their own bindings for dismissal and navigation.

Key Action
Tab Move focus to the next focusable element in document order.
Shift + Tab Move focus to the previous focusable element.
Enter / Space Activate a focused Button.
Escape Close an open Modal (dismisses the dialog and restores focus to the trigger). Also dismisses Tooltip and Menu.
↑ ↓ ← → Navigate items within an open Menu (arrow keys), or cycle through Tabs (left/right arrows).

Focus Management

ARIA Pattern Reference

ComponentPattern
ButtonNative <button> role (or aria-disabled on the asChild path — see Keyboard Interaction above). Decorative sketch outline is aria-hidden="true".
CardNo role by default on React — the caller supplies one. Web Components/React Native automatically expose role="group"/accessibilityRole="summary" when a label is given.
Inputaria-invalid + aria-describedby (pointing at a role="alert" error message) on React when error is set. Label association via <label for>/Radix Label.
Modalrole="dialog" + aria-modal="true", title wired via aria-labelledby. React portals to document.body; WC uses native <dialog> + ::backdrop; RN uses accessibilityViewIsModal.
Alertrole="status" by default, role="alert" for danger variant (assertive). Severity-coloured icon with accessible name from the heading text.
Toastrole="status" (polite) for info/success/warning; role="alert" (assertive) for danger. Auto-dismiss timer; close button labelled “Dismiss”.
Tooltiprole="tooltip" with the trigger linked via aria-describedby. Web: hover (after delay) or focus; dismiss on Escape/blur/leave. RN: tap-to-toggle with accessibilityHint on trigger.
Menurole="menu" with role="menuitem" on items. aria-activedescendant (WC) manages active descendant; React uses roving tabindex. Arrow-key navigation, Escape dismiss.

Screen Reader Testing Checklist

How Color Contrast Validation Works

Every sys.color.* text/background pairing is checked automatically at build time in @ghds/tokens — the check computes the real WCAG relative-luminance contrast ratio for every text/icon/border role against every bg surface, plus explicit text-on-filled-background pairs, and asserts 4.5:1 for normal text or 3:1 for large text/icons/borders. A small, documented set of pairs (disabled text/background, decorative borders) is exempt.

What this means for you: you get the WCAG 2.1 AA guarantee for free as long as you consume sys.color.* (or comp.* tokens that alias them) — the guarantee does not extend to any custom color you introduce outside the token set, or to the exempt pairs above.