Accessibility check
WCAG 2.1 contrast audit of every token pair in the system, computed from the actual CSS values currently loaded. It's user-led — press Run to audit the current implementation; re-run after a theme toggle or token edit to refresh.
Issues this tool surfaced
Running the checker against v1.1 caught five real problems I'd missed in the manual audit. Three were fixed on the spot; two needed a broader hue review and were deferred — both since resolved: F-015 when the accent consolidated to cobalt (v1.3), F-014 when brand moved off green (v1.5).
In light mode, --text-tertiary on --surface-muted scored 4.22:1 — under the 4.5:1 AA threshold for body copy. The earlier audit nudged tertiary once already (v1.1); this checker caught that it wasn't enough. Darkened again to #6E625F, now 4.95:1.
The forest green --brand (#629460) on white was 3.54:1 — fine for dots and focus rings, a fail as text. Deferred since v1.1. Resolved v1.5: brand moved off green to cobalt (#2F4BA6, 8.3:1 on white), closing the brand-on-white fail. The green-as-text concern transferred to the new --success token (same ~3.5:1 physics) and is now scoped there: --success-dark for any text role, bare --success for non-text only. A lint rule against bare --success on text is queued.
White text on the old sky-blue --accent (#0EA5E9) scored 2.77:1 — failed AA and the 3:1 UI threshold. Resolved v1.3: the palette consolidated to a single cobalt accent — --accent is now #2F4BA6 (white on it = 8.3:1, passes both); the sky blue is retained chart-only as --azure-500.
--border-strong on white scores 1.88:1 — under the 3:1 WCAG 1.4.11 threshold for input borders. Darkening to pass would harm the system's hand-drawn warmth. Documented compromise: every input pairs the border with a label outside and a focus shadow ring; the boundary indicator is the multi-cue combination, not the border alone. WCAG considers a non-color signal sufficient.
Disabled text scores 2.24 (card) / 1.93 (muted) in light, 2.90 in dark. WCAG explicitly exempts inactive UI from contrast requirements (SC 1.4.3 / 1.4.11 informative note) — disabled controls should look disabled. The checker flags them for transparency, but no fix is owed. The disabled opacity rule (0.45) combines with this for a stronger visual cue.
How this works
No fixtures, no hardcoded values — every contrast ratio on this page is computed from the live :root custom properties at render time.
The script reads each pair as getComputedStyle(document.documentElement).getPropertyValue('--text-primary'), parses the hex (or rgb) into RGB triplets, and stores both sides.
Each channel is linearised per WCAG 2.1: divide by 255, then either c/12.92 if c ≤ 0.03928 or ((c+0.055)/1.055)2.4. Luminance L = 0.2126·R + 0.7152·G + 0.0722·B.
Ratio = (Llighter + 0.05) / (Ldarker + 0.05). A pair scores up to 21:1 (pure black on pure white). The page reports to one decimal place, matching most external auditors.
Each pair is tagged with its intended use: body text, UI element, or large heading. Thresholds applied per-use — a brand swatch on white isn't held to body-text 4.5:1 because it never carries body text.
What this check doesn't cover
Contrast is one of many a11y axes. Honesty about scope is part of the system.
Focus order, focus traps, and skip links need real DOM walks — covered manually in the v1.1 audit (F-007, F-012).
ARIA roles, live regions, and label associations need testing with NVDA / VoiceOver. Deferred to v1.2 (F-011).
Contrast alone doesn't catch deuteranopia / protanopia confusion (e.g. red/green chips). The system intentionally uses non-colour signals — icons, text labels — in every status component.
WCAG 2.2 AAA wants ≥ 44×44px targets. Tiny Wire is desktop-dense at 32px controls — fails for touch-primary use, intentional for operational tools. Documented as a known boundary.