Contrast checker
tools specs/tools/contrast-checker.kmd
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.
Quando esta spec se aplica
Triggers primários
- Add contrast checker tool
Todos os triggers
- Verify a foreground / background pair meets WCAG
- Check whether a token combination is accessible
- Generate accessible color suggestions for a failing pair
Corpo da especificação
Spec — Contrast checker
Facet Tool 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 |
|---|---|---|
| Manual | Two hex / HCT pickers | Ad-hoc check |
| Token pair | Two token dropdowns | Validate a color-roles.kmd pair |
| Bulk audit | Paste tokens JSON | Run check on every role pair (Light + Dark) |
R2 — Algorithm
Use WCAG 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 color
Relative 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) │
└─────────────────────────────────────────┘
- Sample 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")
- Tick / 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-by-side; 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 ✗ ✗ ← flagged
Failing 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 hard gate when:
- Pair is used for text (
on-*roles) - Pair is used for icon (
on-*-variant+*-containerpairs)
For decorative-only roles (e.g., surface-tint), 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 quality-of-design 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 HCT pickers (sliders arerole="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 pass contrast against the underlying surfacei18n/contract.kmd— sample text locale
Referências
specs/themes/color-roles.kmdspecs/themes/color-customization.kmdspecs/tools/theme-builder.kmdspecs/interaction/states.kmd