Section 07 · Foundations
Accessibility.
A short list, all of it non-negotiable. We sell to people whose work is taken seriously; the product they use should be too.
Contrast
- Body text: AA minimum (4.5:1). Our default
--vp-color-fg-dimon--vp-color-bg≈ 7.5:1. Compliant by default — don't undo it. - UI text (labels, buttons): AA minimum.
- Mute / meta:
--vp-color-fg-mute≈ 4.6:1 — minimum AA. Don't go lower. - Decoration only:
--vp-color-fg-faintnever carries information. - Brand neon on bg:
--vp-color-neonon--vp-color-bg≈ 11:1. Buttons read fine. - Brand neon on neon (button label): the primary button uses
#06140don neon — ≈ 13:1. Keep the dark label.
Focus
One visible ring. Always. Same shape regardless of element.
/* The system focus ring */ :focus-visible { outline: none; box-shadow: 0 0 0 2px var(--vp-color-bg), 0 0 0 4px var(--vp-color-neon); }
- Use
:focus-visible, not:focus— clicking shouldn't draw a ring. - Never
outline: nonewithout replacing it with the ring. - The dark inner band (
--vp-color-bg) prevents the neon from clashing with adjacent borders.
Keyboard
- Tab order: always source order. No
tabindexvalues above 0. - Modal: trap focus, return to opener on close, Esc closes.
- Command palette: ⌘K / Ctrl-K opens. Esc closes. Up/down navigates results. Enter activates.
- Tables: rows are not focusable. The action button inside a row is.
- Tabs: arrow keys move between tabs (not Tab), Tab moves out of the tab list.
Motion
See Motion · prefers-reduced-motion. Implement it once, globally.
Semantics
- Buttons are
<button>. Links are<a href>. A link styled as a button is fine; a button styled as a link is fine; a<div onclick>is never fine. - Form fields have a label.
aria-labelonly when a visible label is genuinely impossible (an icon-only search). - Status messages use
role="status"for non-critical,role="alert"for errors. - Tables that show data use
<table>with<th scope>. Don't fake them with grids unless the column count is dynamic and you accept the screen-reader cost.
Don't
- Don't rely on color alone. Failed probes need a glyph (
XCircle) and the danger colour, not just red text. - Don't auto-dismiss errors. Toast for success — yes. Error stays until the user dismisses it.
- Don't trap pointers. Custom scroll, custom selection — only when there's a measurable benefit.