Accessibility
Whether real users, keyboard-only, screen-reader, low-vision, motor-impaired, can perceive and operate the interface. This file covers the non-contrast surface (names, focus, keyboard, targets, non-color cues); text/UI contrast lives in contrast.md.
Scaffold based on WCAG 2.2 AA and common platform guidance. Treat as a baseline, not exhaustive.
Purpose
Make every interactive element reachable, labeled, and operable without a mouse or perfect vision, so the design holds up for the people who need it most.
Rules
1. Accessible names on every control
Every interactive element exposes a name to assistive tech.
- Images that carry meaning have
alt; decorative images usealt="". - Icon-only buttons/links get an
aria-label(or visually-hidden text). A bare<button><svg/></button>is unnamed. - Inputs have a real
<label>(oraria-labelledby); placeholder is not a label.
2. Visible focus indicator
Keyboard users must always see where they are.
- Never
outline: nonewithout a replacement. - Focus ring clears 3:1 against both the component and the page (see contrast.md).
- Prefer
:focus-visibleso pointer users don't get rings but keyboard users do.
3. Full keyboard operability
Everything you can do with a mouse works from the keyboard.
- Logical tab order; no keyboard traps.
- Custom widgets (menus, tabs, dialogs) implement expected key handling (Esc, arrows, Enter/Space).
- Prefer native elements (
<button>,<a>,<input>) overdiv+onClick, which ship these behaviors for free.
4. Size targets for the input method
Pointer and touch targets are large enough to hit reliably.
- Desktop pointer: minimum 24×24px. Compact controls around 32–40px are normal when a precise mouse or trackpad is the primary input.
- Touch or coarse pointer: aim for 44×44px or larger. The visible control may stay smaller when its invisible hit area reaches 44px and does not overlap another target.
- WCAG 2.2 target size minimum is 24×24 CSS px, subject to its spacing and exception rules. Treat 44px as touch guidance, not a universal desktop floor.
- Spacing between adjacent targets prevents mis-taps.
- The clickable area can extend past the visible glyph via padding.
5. Never encode meaning by color alone
Anyone with color-vision deficiency must get the same information.
- Pair color with text, icon, shape, or pattern (error field: red border and a message; chart series: color and label/marker).
- Links in body text carry a non-color cue (underline).
~300 million people have a colour-vision deficiency, 1 in 12 men. Red vs green is the most common miss, so a red border alone can’t carry an error.
6. Structure and landmarks
Screen readers navigate by structure.
- One
<h1>per view; heading levels don't skip. - Landmark elements (
<nav>,<main>,<header>,<footer>) frame the page. - Lists use list markup; tables use
<th>+ scope.
7. Respect user settings
prefers-reduced-motionhonored (see motion.md).- Layout survives 200% zoom and user font-size changes (use
rem, not fixedpxtype).
Assistive technology & standards
- Baseline: WCAG 2.1 level AA. A good, often legally-required floor. Aim to pass it for contrast, targets, and non-text UI (≥3:1 for borders/icons/controls).
- Design for screen readers. Elements are read out of context, so headings and link/ action text must be descriptive on their own ("View pricing", not "Read more"). This is a copy rule too, see content-copy.md.
- Design for screen magnifiers (more common than screen readers). The user sees only a small slice of the screen at a time, so keep related actions close together and left-align primary actions so they aren't scrolled out of view or missed.
- Good accessibility = good usability for everyone. Sufficient contrast, low interaction cost, and low cognitive load help sighted, able users too, and everyone hits temporary or situational impairment (bright sun, one hand, an injury).
- When you replace text with a visual/motion cue (a shake for an invalid field, a
colour change for a state), provide an accessible equivalent:
aria-invalid, an accessibility hint, or a live-region announcement. Motion is never the only channel (see motion.md).
Common mistakes
| Tell | Fix |
|---|---|
| Icon button with no label | Add aria-label |
outline: none with no :focus-visible style |
Restore a visible focus ring |
<div onClick> acting as a button |
Use <button> |
| Placeholder used as the only label | Add a real <label> |
| Error shown only as red border | Add text + icon |
| Touch target < 44px | Grow its hit area or add spacing; desktop pointer controls may be smaller |
| Heading levels skip (h1 → h4) | Fix hierarchy |
Do / Don't
| Do | Don't |
|---|---|
| Name every control | Ship unnamed icon buttons |
Keep a visible :focus-visible ring |
outline: none globally |
| Use native interactive elements | Rebuild buttons from divs |
| Pair color with a second cue | Rely on hue alone |
| Use ≥24px for desktop pointer and aim for ≥44px on touch | Apply one minimum to every input method |
Audit
- High: unnamed control, no visible focus, keyboard-inoperable widget, meaning by color alone, target < 24px, or an unusably small/overlapping touch target
- Medium: skipped heading levels, missing landmarks, no reduced-motion handling
- Low: minor label wording, non-underlined body links
Verify on the rendered page: tab through it with no mouse, and check names in the accessibility tree, not just the source.