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:
- Tab through it. Every control reachable, in a sensible order.
- Focus ring visible at every stop — this site uses
outline: 3px solid #a7483f; outline-offset: 2px. Keep it. - Operate it with Enter and Space. Escape closes anything that opened.
- Nothing is reachable only by hover or only by pointer.
- 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-labelon regions. Preserve every one during migration; they are asserted inverify.mjs. - One
<h1>per page. Heading levels never skip. - Every image needs
alt. Decorative images getalt="".
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.