Skip to content

Accessibility

Accessibility is a first-class design constraint in Human. Every token, component, and animation is validated against WCAG 2.2 AA (minimum) with AAA targets for critical paths.

ContextMinimum RatioStandard
Normal text (< 24px)4.5:1WCAG AA
Large text (≥ 24px or ≥ 19px bold)3:1WCAG AA
UI components & graphical objects3:1WCAG AA
High-contrast mode text7:1WCAG AAA

All semantic color combinations are tested:

  • --hu-text on --hu-bg — passes AA
  • --hu-text-muted on --hu-bg — passes AA
  • --hu-accent-text on --hu-bg — passes AA (note: accent-text is distinct from accent for this reason)
  • --hu-on-accent on --hu-accent — passes AA
  • --hu-error on --hu-bg — passes AA

Every interactive element shows a visible focus indicator using the token system:

:focus-visible {
outline: var(--hu-focus-ring-width) solid var(--hu-focus-ring);
outline-offset: var(--hu-focus-ring-offset);
}

In high-contrast mode, --hu-focus-ring is white and --hu-focus-ring-width increases to 3px.

Modal dialogs and sheets trap focus:

  1. First focusable element receives focus on open
  2. Tab cycles through focusable elements within the component
  3. Shift+Tab cycles backward
  4. Escape closes the modal and returns focus to the trigger

Composite widgets use roving tabindex for arrow key navigation:

  • Tab bar: Left/Right arrows move between tabs
  • Menu: Up/Down arrows move between items
  • Toolbar: Left/Right arrows move between buttons

The prefers-reduced-motion: reduce media query is respected throughout:

@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}

Specifically:

  • All micro-physics transforms are disabled
  • Glass blur is preserved (not motion-related)
  • Opacity transitions may remain (they don’t trigger vestibular responses)
  • Loading spinners use reduced animation

Windows High Contrast Mode is supported via forced-colors: active:

@media (forced-colors: active) {
.hu-button {
border: 2px solid ButtonText;
background: ButtonFace;
color: ButtonText;
}
.hu-button:hover {
background: Highlight;
color: HighlightText;
}
.hu-card {
border: 1px solid CanvasText;
}
}

System color keywords used:

  • Canvas / CanvasText — page background/text
  • ButtonFace / ButtonText — button background/text
  • Highlight / HighlightText — selection/hover states
  • LinkText — links

For users who prefer reduced transparency (prefers-reduced-transparency: reduce):

@media (prefers-reduced-transparency: reduce) {
.glass,
.glass-subtle,
.glass-prominent {
backdrop-filter: none;
background: var(--hu-bg-surface);
opacity: 1;
}
}

Use .sr-only for screen-reader-only content:

.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
ComponentRoleKey ARIA Attributes
Modaldialogaria-modal, aria-labelledby
Toastalertaria-live="polite"
Badgearia-label for status
Toggleswitcharia-checked
Command Palettecomboboxaria-expanded, aria-activedescendant
Sidebar navnavigationaria-label

Dynamic content updates use appropriate aria-live values:

  • Toasts: aria-live="polite" — announced after current speech
  • Errors: aria-live="assertive" — announced immediately
  • Status: aria-live="polite" with aria-atomic="true"

Accessibility is validated at multiple levels:

  1. Automated: axe-core via Playwright E2E tests catches common violations
  2. CI: Lighthouse accessibility audit runs on every PR (target: 95+)
  3. Manual: Keyboard navigation and screen reader testing checklist
Terminal window
cd ui && npx playwright test e2e/accessibility.spec.ts