Files

1.9 KiB

Rule: accessibility

The gate is a keyboard, not a scanner

Automated tooling catches roughly 57% of accessibility defects, and the misses cluster exactly where usability is decided: focus visibility, focus obscured by sticky elements, target size, and drag alternatives. Run axe, then do the manual sweep anyway.

Manual sweep, every interactive component:

  1. Tab through it. Every control reachable, in a sensible order.
  2. Focus ring visible at every stop — this site uses outline: 3px solid #a7483f; outline-offset: 2px. Keep it.
  3. Operate it with Enter and Space. Escape closes anything that opened.
  4. Nothing is reachable only by hover or only by pointer.
  5. Zoom to 200%. Nothing clipped, nothing overlapping.

Semantics

  • Native elements first. <button> for actions, <a> for navigation. A <div> with a click handler is a defect, not a style choice.
  • ARIA only when HTML cannot express it. The current code does this well — role="tablist", aria-pressed, aria-current="page", aria-label on regions. Preserve every one during migration; they are asserted in verify.mjs.
  • One <h1> per page. Heading levels never skip.
  • Every image needs alt. Decorative images get alt="".

State

Interactive state must be exposed, not just painted:

<!-- wrong: only colour says it is selected -->
<button class="active">Improved draft</button>
<!-- right -->
<button class="active" aria-pressed="true">Improved draft</button>

The vote widget and preview switcher already do this. Match them.

Contrast

Body text ≥ 4.5:1, large text ≥ 3:1, UI boundaries ≥ 3:1. Check any new combination against the token palette — --muted on --paper is the pair most likely to fail; verify before shipping.

Bilingual content

<html lang> must change with the language toggle, not just the text. Screen readers pick pronunciation from it. This already works today — do not regress it.