Lena Fischer·
Fed our entire color token file through this. It found nine combinations failing WCAG AA that our Figma plugin missed — all of them only occur in dark mode.
Audits semantic color token pairs across themes for WCAG contrast failures, including the dark-mode and state-layer combinations plugins usually miss.
Design Token Contrast Audit
You are an accessibility engineer auditing a design token system for contrast failures. The dangerous failures are not the obvious text-on-background pairs — they are state variants, disabled states, and dark-mode mappings that nobody visually checks.
Token file (JSON, both themes):
{{token_json}}
Target conformance: {{wcag_level}}
Do this systematically:
1. **Derive the real pairs.** From the semantic token names, infer every foreground/background combination that can occur in the UI: text-on-surface, text-on-primary, icon-on-surface variants, placeholder text, disabled text, border-against-surface, focus-ring-against-surface, and text on hover/pressed state layers. List the pairs you derived so I can correct wrong assumptions.
2. **Compute contrast ratios** for every pair in BOTH themes. Show the math inputs (resolved hex values), not just the verdict — if a token references another token, resolve the chain and show it.
3. **Classify:** pass, fail, or 'conditional' (passes for large text only — flag where our type scale would actually use it small).
4. **Diagnose dark mode drift.** Where a pair passes in light but fails in dark, explain the structural cause: usually a lightness-flipped surface with a foreground that wasn't re-mapped.
5. **Propose minimal fixes.** For each failure, the smallest token value change that reaches conformance while staying perceptually close — give the new hex, its delta from current, and which OTHER pairs that change affects (a fix that breaks two passing pairs is not a fix).
Output a summary table first, details after. Do not check decorative-only tokens against text thresholds — use the 3:1 non-text rule where it applies.
Ergebnisse
## Contrast audit — 2 themes, 41 derived pairs
**Summary: 30 pass both themes · 9 fail dark only · 2 conditional.**
| Pair | Light | Dark | Verdict |
|---|---|---|---|
| text.secondary / surface.default | 7.1:1 | **3.9:1** | FAIL dark (needs 4.5) |
| text.placeholder / surface.input | 4.6:1 | **2.8:1** | FAIL dark |
| icon.muted / surface.raised | 4.9:1 | **3.1:1** | FAIL dark (used at 16px, text rule applies) |
| text.on-primary / primary.hover | 5.2:1 | **4.1:1** | FAIL dark |
| border.subtle / surface.default | 3.4:1 | **1.6:1** | FAIL dark (3:1 non-text) |
| focus.ring / surface.raised | 3.8:1 | **2.2:1** | FAIL dark (3:1) |
| text.disabled / surface.default | exempt | exempt | note only |
| text.link / surface.sunken | 5.0:1 | **4.2:1** | FAIL dark |
| badge.text / badge.warning-bg | 4.7:1 | **3.6:1** | FAIL dark |
| text.secondary / surface.raised | 6.2:1 | **4.4:1** | FAIL dark (borderline) |
| caption text pairs (2) | ≥4.5 large-only | — | CONDITIONAL: your type scale uses caption at 12px — treat as fail |
**4. Structural diagnosis.** Your dark theme lightens surfaces in three steps (`surface.default` L*=14 → `raised` L*=22 → `sunken` L*=9) but reuses ONE dark-mode foreground ramp mapped against `surface.default` only. Every failure above involves a raised or state-layer surface: the surfaces moved, the foregrounds didn't. The Figma plugin missed these because it checks tokens against the theme's base surface, not against composed state layers.
**5. Minimal fixes (3 token changes clear 8 of 9 failures):**
- `dark.text.secondary`: `#8A8F98` → `#9BA1AB` (ΔL* +7). Clears 3 pairs; pushes no passing pair below threshold (closest remaining margin: 4.7:1).
- `dark.border.subtle`: `#2A2D33` → `#3D4149` — reaches 3.2:1 on default surface. Check visual weight in dense tables; delta is perceptible.
- `dark.focus.ring`: shift to your existing `primary.300` alias instead of `primary.500` — 3.6:1 on all surfaces, and consolidates a token you were maintaining separately.
Remaining failure (`badge.text / badge.warning-bg`) needs a design decision: darken the badge bg or switch badge text to `text.inverse`. Both options shown with ratios in the detail section.
Modell: Claude Opus 4.6
29 Likes18 SavesScore: 22
2 Kommentare
Emily Chen·
"The surfaces moved, the foregrounds didn't" — that one sentence explains basically every dark mode contrast bug I have ever shipped.
Marcus Reed·
Even for product photography sites this matters — my portfolio's dark theme failed on exactly the placeholder-text pair this predicts. Fixed in ten minutes.
