Contrast checker
In-browser tool at `kds.koder.dev/tools/contrast/` that validates foreground-background color pairs against WCAG 2.2 AA / AAA. Material parity (`/foundations/accessible-design/accessibility-basics` color tools). Used inline by Theme Builder; standalone for ad-hoc checks.
Spec — Contrast checker
Facet *ool*of Koder Design. Material parity: https://m3.material.io/foundations/accessible-design.
URL: https://kds.koder.dev/{locale}/tools/contrast/
What it does
Given two colors (foreground + background), output:
- Contrast ratio (e.g., 4.7 : 1)
- Pass / fail vs WCAG 2.2 AA (4.5 : 1 normal text, 3.0 : 1 large text)
- Pass / fail vs WCAG 2.2 AAA (7.0 : 1 normal text, 4.5 : 1 large
text)
- Pass / fail vs non-text contrast (3.0 : 1 for UI components +
graphical objects)
- Suggested adjustments if failing — preserves hue, tweaks tone to
pass
R1 — Input modes
| Mode | Source | Use |
|---|---|---|
| *anual* | Two hex / HCT pickers | Ad-hoc check |
| *oken pair* | Two token dropdowns | Validate a color-roles.kmd pair |
| *ulk audit* | Paste tokens JSON | Run check on every role pair (Light + Dark) |
R2 — Algorithm
Use *CAG 2.2 relative luminance*formula (sRGB):
contrast = (L1 + 0.05) / (L2 + 0.05)
where L1 = relative luminance of lighter color
L2 = relative luminance of darker colorRelative luminance per WCAG: linearize each sRGB channel (c_lin = c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ^ 2.4), then L = 0.2126 * R + 0.7152 * G + 0.0722 * B.
Reference impl lives in tools/design-gen/internal/color/contrast.go.
APCA (next-gen contrast metric) is NOT used for pass / fail in v1 — WCAG 2.2 remains the legal standard. APCA shown as informational second metric only (opt-in toggle).
R3 — Output panel
┌─────────────────────────────────────────┐
│ Foreground Background Sample text │
│ ████ ░░░░ AaBbCc 123 │
│ #1976D2 #FFFFFF │
│ │
│ Contrast ratio: 4.59 : 1 │
│ │
│ WCAG 2.2 │
│ ✓ AA normal text (≥ 4.5) │
│ ✓ AA large text (≥ 3.0) │
│ ✗ AAA normal text (≥ 7.0) │
│ ✓ AAA large text (≥ 4.5) │
│ ✓ Non-text (≥ 3.0) │
│ │
│ Suggestion: shift FG to #1565C0 → │
│ contrast 5.31 : 1 (passes AA AAA-large) │
└─────────────────────────────────────────┘- *ample text* rendered in both BG / FG with proper font sizes
(16 px for "normal", 18 pt = 24 px or 14 pt bold = ~20 px for "large")
- *ick / cross marks* tone uses
success/errorcolor roles(passing the contrast spec themselves)
R4 — Suggestion engine
If FG/BG fails AA, suggest a shifted FG (preserving hue):
- Step tone in HCT space toward black or white in 5-unit increments
- Pick the smallest shift that passes AA
- Show before / after side
byside; user picks or refines
When no shift passes (e.g., yellow on white), display:
"No tone shift preserves hue while passing AA. Consider a different role or surface."
R5 — Bulk audit (Theme Builder integration)
Input: full theme JSON (the tokens.json Style Dictionary file from tools/design-kit-export.kmd § R2).
Output table:
Role Ratio AA AAA
──────────────────────────────────────────────────
primary / on-primary 7.2 : 1 ✓ ✓
secondary / on-secondary 6.8 : 1 ✓ ✓
surface / on-surface 12.1 : 1 ✓ ✓
...
error-container / on-error-container 3.9:1 ✗ ✗ ← flaggedFailing rows highlighted in red; click to open Manual mode with that pair preloaded for tuning.
R6 — Token role validation
For a color-roles.kmd role pair (e.g., primary + on-primary), the checker enforces WCAG 2.2 AA as a *ard gate*when:
- Pair is used for text (
on-*roles) - Pair is used for icon (
on-*-variant+*-containerpairs)
For decorativeonly roles (e.g., `surfacetint`), no gate — checker shows ratio informationally.
R7 — Color-blindness simulation
Toggle: re-runs the check against simulated palette (Protanopia / Deuteranopia / Tritanopia / Achromatopsia).
If a pair passes WCAG 2.2 AA in normal vision but fails for one of the simulations, surface a soft warning ("Accessible to normal vision but low contrast for Deuteranopia (3.2 : 1)").
WCAG 2.2 does NOT require passing for simulated vision; this is a qualityofdesign warning, not a fail.
R8 — Accessibility of the checker itself
- Tickers (✓ / ✗) accompanied by accessible text labels — color +
symbol + text triple
- Result ratio announced in live region on change
- Color pickers from
themes/color-customization.kmd-compatible HCTpickers (sliders are
role="slider"etc.)
R9 — Performance
- Computation < 5 ms per pair (pure math; runs at every input change)
- Bulk audit: < 200 ms for 18 roles × 2 modes (Light + Dark) = 36
pairs
R10 — Forbidden patterns
- ❌ Using WCAG 2.0 (outdated); use 2.2
- ❌ Returning APCA pass / fail as primary metric (WCAG remains the
contract)
- ❌ Suggesting colors outside the current preset's palette in
Theme Builder context (use bulk audit then theme-tune flow)
- ❌ Reporting non-text contrast as text (3.0 : 1 is too low for
text)
- ❌ Hardcoding sample text "Lorem ipsum" without i18n localization
Cross-link
themes/color-roles.kmd— role pair definitionsthemes/color-customization.kmd— color-blindness packstools/theme-builder.kmd— embedded contrast checkerinteraction/states.kmd— focused / hover overlays must also passcontrast against the underlying surface
i18n/contract.kmd— sample text locale