Files
design-playbook/principles/05-accessibility.md
T
Marcos Paulo 79fb875445 replace Python scripts with a Go playbook CLI
One static binary with the rules embedded: search, rule, list, doc, contrast,
tokens, skills install. Output matches the Python scripts on the checked queries.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 16:11:03 -03:00

9.4 KiB

Accessibility

Target: WCAG 2.2 Level AA, plus the AAA items listed here as house defaults. Sources: S01 (W3C new in 2.2), S02 (contrast), S03 (target size), S07 (APG "read me first"), S08 (COGA), S16 (inclusive design principles), V04 (Chrome A11ycasts focus), V06 (Hawkins on accessible design systems), S11 (Vercel). WCAG 2.2 is backward compatible: meeting it also meets 2.0 and 2.1. SC 4.1.1 Parsing was removed in 2.2.

Automated tools catch only a fraction of defects (one vendor cites roughly 57% from a 2024 Deque study: treat as indicative). Always add keyboard and screen reader passes (checklists/a11y-audit.md).

Perceivable

A11Y-01 Text contrast >= 4.5:1; large text >= 3:1 (SC 1.4.3, AA)

Large = 18 pt (24 px) or 14 pt bold (about 18.5 px). No rounding: 4.499 fails. Hover and focus text states count. Brand guidelines are not an exemption; logotypes and inactive controls are. Compute with playbook contrast.

A11Y-02 Non-text contrast >= 3:1 (SC 1.4.11, AA)

Input borders, icon-only controls, focus indicators, chart marks against their adjacent colors. A pale gray input border on white usually fails.

A11Y-03 Do not rely on color alone (SC 1.4.1, A)

Add text, icon, shape or thickness (VIS-10).

A11Y-04 Reflow and zoom (SC 1.4.4, 1.4.10, AA)

Content works at 200% text zoom and at 320 CSS px width without two-dimensional scrolling. Never disable browser zoom. Use rem, flex/grid, no fixed heights on text containers.

A11Y-05 Text alternatives (SC 1.1.1, A)

Informative images get meaningful alt; decorative get alt="" or aria-hidden; icon-only buttons get an accessible name (A11Y-14).

A11Y-06 Captions and transcripts for media (SC 1.2.x)

Caption speech and meaningful sound; transcripts for audio-only; essential visuals described; media controls keyboard-operable.

Operable

A11Y-07 Everything works with a keyboard (SC 2.1.1, A) and no trap (2.1.2)

Tab/Shift-Tab to move, Enter/Space to activate, Esc to dismiss, arrows inside composite widgets (menus, tabs, radios, listboxes) per the APG patterns. Native elements get this free: button, a[href], input, select.

A11Y-08 Logical focus order, from DOM order (SC 2.4.3)

Tab order follows DOM order (V04). CSS float, absolute positioning, flex order and grid placement can desync visual and tab order: fix by moving the element in the DOM, not by positive tabindex. Never use tabindex > 0. Only interactive elements are tab stops; do not add tabindex="0" to static text. Test by tabbing the whole page.

A11Y-09 Target size: 24x24 CSS px minimum (SC 2.5.8, AA); 44x44 is the house default

2.5.8 passes smaller targets only if a 24 px circle around each does not overlap another target, or an equivalent control exists, or the target is inline in text, user-agent default, or essential. 2.5.5 (AAA) asks 44x44, matching Apple's 44 pt and Vercel's mobile guidance: use 44 on touch, expand the hit area with padding or a pseudo-element without enlarging the visual. 44 px figure comes from 2.5.5 and platform guides, not from the S03 page. Measured values by screen position (edges need more): PAT-06.

A11Y-10 Visible, unobscured focus (SC 2.4.7 AA, 2.4.11 AA new in 2.2)

Every focusable element shows a visible indicator. Sticky headers, cookie bars and chat widgets must not fully hide the focused element: add scroll-padding-top/scroll-margin equal to the sticky height. Use :focus-visible (not :focus alone) and style it.

A11Y-11 Focus indicator quality (SC 2.4.13, AAA, house default)

At least a 2 CSS px perimeter and 3:1 contrast between focused and unfocused. A 2 px outline with 2 px offset, double ring (inner light, outer dark) so it works on any background. Prefer outline; if using box-shadow for radius reasons, add a transparent outline so forced-colors mode still shows a ring.

A11Y-12 Dragging has an alternative (SC 2.5.7, AA, new in 2.2)

Reorder, sliders, resize, map pan must work with single taps/clicks or buttons (move up/down, +/-, numeric input) unless dragging is essential.

A11Y-13 Respect user settings

Honor prefers-reduced-motion (MOT-07), prefers-color-scheme, prefers-contrast, forced-colors, font-size and zoom, system language. Do not use px for font sizes in a way that blocks scaling.

A11Y-14 Accessible name, role, value (SC 4.1.2, A)

Every control has a name that includes its visible label text (SC 2.5.3 Label in Name). Icon-only: aria-label or visually hidden text. Decorative SVG: aria-hidden="true". State is exposed (aria-expanded, aria-pressed, aria-current, aria-invalid).

"Skip to content" first in the tab order; one main; hierarchical headings, no level skipping for styling; accurate <title> per view; anchor targets have scroll-margin-top.

A11Y-16 Time and motion (SC 2.2.1, 2.2.2, 2.3.1)

No hard session timeouts without warning and extension; pause/stop for moving content; no flashing more than three times per second.

Navigation uses <a href> so Cmd/Ctrl-click, middle-click and right-click work. Actions use <button>. A link styled like a button stays a link, document it (V06). Never div onClick.

A11Y-18 Gesture alternatives

Swipe, pinch, long-press, path gestures have tap/click or keyboard equivalents (S11). Multi-finger and motion (shake) gestures are never the only way.

Understandable

A11Y-19 Labels, instructions, errors (SC 3.3.1, 3.3.2, 3.3.3, A/AA)

Visible label per field (placeholder is not a label). Errors in text, next to the field, programmatically associated (aria-describedby), say what is wrong and how to fix it, and are not color-only (V06 postal-code example). On submit, move focus to the first error and announce with a polite live region.

A11Y-20 Consistent navigation and help (SC 3.2.3, 3.2.4, 3.2.6 new, A/AA)

Repeated navigation and identification stay in the same order and wording. Help (contact, chat, support link) keeps the same relative place across pages.

A11Y-21 No redundant entry (SC 3.3.7, A, new in 2.2)

Do not ask for information already given in the same process; prefill or let the user select it.

A11Y-22 Accessible authentication (SC 3.3.8, AA, new in 2.2)

No cognitive function test (memorize, transcribe, solve) unless an alternative exists. Allow paste in password and OTP fields and password managers; support passkeys and email links; CAPTCHAs need a non-cognitive alternative.

A11Y-23 Plain language and cognitive load (COGA, S08)

Short sentences, literal language, no double negatives, state costs up front, keep controls from moving unexpectedly, let users go back, minimize interruptions, ask only for needed information, accept different input formats, avoid data loss and timeouts.

A11Y-24 Language (SC 3.1.1, 3.1.2)

lang on <html> and on foreign phrases; translate="no" on brand names and code.

Robust

A11Y-25 Native first; no ARIA is better than bad ARIA (S07)

A role is a promise: adding role="button" obliges you to deliver Enter/Space, focus and state. ARIA cannot make a div behave. Use button, dialog (<dialog> with showModal()), details, input type=checkbox, select before custom widgets. If building a custom widget, copy the APG pattern's keyboard map and roles exactly and test with a screen reader.

A11Y-26 Dialogs and overlays

Move focus in on open, trap it while modal (use inert on the background or native modal <dialog>), restore focus to the trigger on close, Esc closes, label via aria-labelledby. Non-modal popovers do not trap.

A11Y-27 Live regions

Toasts, async validation and result counts use aria-live="polite" (assertive only for urgent). The live region must exist in the DOM before the text changes. Undo toasts must be reachable by keyboard and stay long enough (INT-06).

A11Y-28 Do not break assistive tech or extensions

Do not block paste, do not disable zoom, keep the accessibility tree accurate (verify in browser devtools), leave password managers working, support personalization and translation (COGA objective 8).

Inclusive design principles (S16)

Comparable experience; consider situation (glare, one hand, noise, stress); be consistent; give control; offer choice; prioritise content; add value. Design for permanent, temporary and situational limits together: one-handed use, bright sunlight, a broken wrist, a loud room.

Accessible design systems: who owns what (V06, Hawkins)

  • Design tokens carry many accessibility decisions (contrast pairs, border thickness, focus color, target sizes) but not all.
  • Design every state of every component (default, hover, focus, active, disabled, error, loading, selected) in the system, so organisms inherit them.
  • UX owns structure: error text placement, reading and focus order, wayfinding, alternative interactions, redundant coding (never color alone).
  • UI owns contrast, type, focus visuals, motion.
  • Content owns labels, error wording, alt text, reading level.
  • Documentation must include keyboard interactions, pointer interactions, screen reader expectations and component rules (a button looks and acts like a button; a link-styled-button is a link). Keyboard docs are the biggest gap in the industry (V06).
  • Child themes inherit accessibility tokens from the parent; they may change values (fuchsia vs green focus color) but must keep the contrast and thickness invariants. Encode invariants as tests, not prose.