Accessibility is a typography problem first and a contrast problem second. Then it's a focus problem. Then a motion problem. We treat it as a property of the system, not a checklist on the way out.
Body text and signals all pass AA against both the ivory and ivory-2 surfaces. Red on ink only clears AA-Large, so on dark themes the red signal must be ≥18px or ≥14px bold — that's why the dark deck slides set their red mono caps at 18px. Grey-300 exists only for hairlines and disabled state; never use it for text.
Keyboard and screen-reader users get an unmistakable signal — the only red outline anywhere in the system.
:focus-visible is the rule. :focus alone is banned (it fires on mouse click and produces noise). Inputs on a hairline border get a 2px red bottom-shadow; surfaces get a 2px red outline with a 2px offset.
§C · Skip link · first stop in the tab order
The first focusable thing on every page. Hidden until :focus-visible; then it lands at top-left, in mono caps, on ink:
Skip to main content
§D · States · what an interactive element looks like in each register
Disabled state lowers opacity to 0.4 and switches stroke to grey-300 — never grey-500 (which would still pass AA and make the control look dimmed but real). Disabled means dead.
§E · Motion · the prefers-reduced-motion contract
SPINNER900ms continuous rotate (default)slows to 4s rotation when prefers-reduced-motion
LIVE DOT1.4s pulse · opacity 1 → 0.4 → 1animation: none · stays at full opacity
SKELETONshimmer · 1.4s linear, infinitestatic --ivory-2 block (no shimmer)
DECK SLIDES280ms ease-out cross-fadeinstant cut · no fade
CHART REVEALSstaggered 60ms per seriesall series appear together · static
SCROLLnative browser smooth-scroll if user has itnever scroll-behavior: smooth in our CSS
Every animation in the system has a @media (prefers-reduced-motion: reduce) branch. We never remove the affordance — a spinner still spins, just slower. The brand stays recognizable; the strobe goes away.
§F · Semantics · what the markup tells the screen reader
§ Required
Headings nest. One h1 per page. Sections use h2 through h4 in order.
Landmarks.main, nav, aside, footer on every page. Skip link targets main.
Labels are real.<label for> on every input. Placeholder is never the label.
Modals trap focus. Tab cycles inside; Esc closes; focus returns to the trigger.
Live regions. Toasts post into role="status" for info/success and role="alert" for warn/error.
Icons are aria-hidden="true" when redundant with text. Standalone (close, search) carry role="img" + <title>.
§ Banned
A div with a click handler — use a button
Color as the only signal — every red item also carries a label, dot, or icon
outline: none without a replacement
Scroll-jacking, parallax, autoplay video
Tooltips for primary content — they're keyboard-hostile
Placeholder text doing the job of a label
Tabindex values higher than 0
Modal opens and focus stays on the page underneath
Body AAA on both ivory surfaces ·
Captions AA · grey-500 only ·
Red on ink AA-Large · ≥18px or ≥14px·700 ·
Focus 2px red · 2px offset · :focus-visible only ·
Motion every animation honors prefers-reduced-motion ·
Disabled opacity 0.4 + grey-300 stroke ·
DocA11Y.md