Foundations / Accessibility
Accessibility.
WCAG 2.2 AA is the floor. Keyboard-first, motion-considerate, contrast-checked — across the portal and every component we ship.
Targets
The non-negotiables. If a surface fails any of these, it ships with a fix, not a waiver.
| Target | Value | Detail |
|---|---|---|
| Contrast | 4.5:1 body · 3:1 large + UI | WCAG 2.2 AA. Body copy ≥ 16px must hit 4.5:1; ≥ 18.66px or bold ≥ 14px and non-text UI hit 3:1. |
| Touch targets | ≥ 44 × 44 CSS px | Hit area, not visual size. Use padding on the interactive element to reach the floor. |
| Focus ring | 1px solid var(--fg), 2px offset | Set globally in app.css. Visible only on :focus-visible — mouse clicks stay clean. |
| Motion | Respects prefers-reduced-motion | Press-scale and per-character stagger both collapse to instant transitions under the reduce query. |
| Keyboard | Tab · Space / Enter · Escape | Every interactive surface reachable via Tab, activated via Space or Enter, dialogs trap and Escape closes. |
Focus visualization
Tab through the three controls to see the ring. We use :focus-visible only — mouse clicks never light it up.
Contrast tokens
Computed against the cream surface. Every product text colour clears AA for its size class.
Aa Body copy at 15px sits here, like this line.
Aa Body copy at 15px sits here, like this line.
Aa Body copy at 15px sits here, like this line.
Motion
Press feedback is universal across buttons. Per-character stagger lives on the marketing-side hero only. Both honour reduce-motion via @media.
The press uses transform: scale(0.97) at 100ms. Under @media (prefers-reduced-motion: reduce) the transform is removed entirely and
the button state changes without animation.
Screen reader patterns
- Semantic HTML first. Use
<button>,<nav>,<main>before reaching for ARIA. Most components need noroleat all. - aria-label on icon-only buttons. Every standalone icon trigger gets a label (close, copy, dismiss). No exceptions.
- aria-busy for loading. Set on the region that is fetching, not the whole page, so the rest stays navigable.
- aria-current for nav. Sidebar links and tabs set
aria-current="page"oraria-current="true"on the active item. - Live regions for toasts. Polite by default, assertive only for errors that block the user.