checkThemeContrast(resolveTheme(theme), { exceptions }) from uxdsl/theme — the same call uxdsl theme --contrast makes in a terminal, where a failing pair exits 1. It checks every text, placeholder and border pair the Surface, Button and Input engines define, in light and dark mode, at every configured breakpoint, against 4.5:1 for text and 3:1 for borders. Switch the theme in the header and the report below is recomputed; the browser walk compares it with the same call in Node.
It checks the colors a theme resolves to, not a rendered page: it is not an accessibility certification, and it says nothing about focus order, labels or semantics, which stay the job of your HTML.
The packaged base theme and this site's four themes pass with zero failing pairs. Passing is not "every pair passed": uxdsl/theme/base.contrast-exceptions.json ships three pattern records, one per canvas-identity family — surface, light, dark — used as a tone on a role that draws it on the page itself (outlined, flat, underline, a contained Input's focus border). That draws the canvas color on the canvas, and no value of the family can fix it without changing what the family is. Each excepted pair is listed below with its real ratio and the record that covers it; a pattern that covers nothing, a malformed record or a duplicate id fails the verdict.
{ "id": "canvas-identity-tone-light", "tone": "light", "against": "ambient", "reason": "…" }
A project passes its own exceptions file with { exceptions }. Use surface, light and dark as a tone only on contained, where they are a fill with their own contrast on top — that text pair is still checked.
| Exception | Kind | Covers | Reason |
|---|---|---|---|
canvas-identity-tone-surface | pattern | 42 pair(s) | palette.surface is the page's own canvas: surface.main IS the ambient background every border and every transparent-background role is measured against. Asking for it as a tone on outlined, flat or underline — or as the focus border of a contained Input — draws the canvas color on the canvas, about 1:1 in either mode, whatever value the family holds. No color choice can fix that without giving `surface` a second, contradictory meaning as a text color, so it is a property of the role, not a color to correct. Use `surface` as a tone only on `contained`, where it is a fill with its own `contrast` on top; that text pair is measured against the role's own background and is NOT covered by this pattern. |
canvas-identity-tone-light | pattern | 46 pair(s) | palette.light is a near-canvas fill family (a near-white surface in light mode, a raised panel in dark mode), not a text or border color. Used as a tone on outlined, flat or underline — or as the focus border of a contained Input — its main/dark are drawn directly on the page, 1.10:1 to 1.95:1 with the shipped values. Darkening it until that passes would stop it being `light`. Use `light` as a tone only on `contained`, where it is a fill with its own `contrast` on top; that text pair is measured against the role's own background and is NOT covered by this pattern. This subsumes the single exact record the file shipped before (surface.outlined, light tone, base text, #f1f5f9 on #ffffff, 1.10:1). |
canvas-identity-tone-dark | pattern | 23 pair(s) | palette.dark is the inverse canvas: a near-black fill for inverted panels. In dark mode its main/dark sit next to the page's own near-black background, so as a tone on outlined, flat or underline — or as the focus border of a contained Input — it is drawn dark on dark, about 1.04:1. In light mode the same tone passes and nothing is excepted. Lightening it until dark mode passes would stop it being `dark`. Use `dark` as a tone only on `contained`, where it is a fill with its own `contrast` on top; that text pair is measured against the role's own background and is NOT covered by this pattern. |
| Group | Failing |
|---|
| Mode | Component | Tone | State | Pair | Width ≥ | Ratio | Needs |
|---|
| Mode | Component | Tone | State | Pair | Against | Ratio | Needs | Exception |
|---|---|---|---|---|---|---|---|---|
| light | surface.outlined | surface | base | text | ambient | 1.00 | 4.5 | canvas-identity-tone-surface |
| light | surface.outlined | surface | base | border | ambient | 1.00 | 3 | canvas-identity-tone-surface |
| light | surface.outlined | light | base | text | ambient | 1.10 | 4.5 | canvas-identity-tone-light |
| light | surface.outlined | light | base | border | ambient | 1.10 | 3 | canvas-identity-tone-light |
| light | surface.flat | surface | base | text | ambient | 1.00 | 4.5 | canvas-identity-tone-surface |
| light | surface.flat | light | base | text | ambient | 1.10 | 4.5 | canvas-identity-tone-light |
| light | button.outlined | surface | base | text | ambient | 1.00 | 4.5 | canvas-identity-tone-surface |
| light | button.outlined | surface | base | border | ambient | 1.00 | 3 | canvas-identity-tone-surface |
| light | button.outlined | surface | hover | text | ambient | 3.13 | 4.5 | canvas-identity-tone-surface |
| light | button.outlined | surface | focusvisible | text | ambient | 3.13 | 4.5 | canvas-identity-tone-surface |
| light | button.outlined | surface | selected | border | ambient | 1.00 | 3 | canvas-identity-tone-surface |
| light | button.outlined | light | base | text | ambient | 1.10 | 4.5 | canvas-identity-tone-light |
| light | button.outlined | light | base | border | ambient | 1.10 | 3 | canvas-identity-tone-light |
| light | button.outlined | light | hover | text | ambient | 1.23 | 4.5 | canvas-identity-tone-light |
| light | button.outlined | light | hover | border | ambient | 1.23 | 3 | canvas-identity-tone-light |
| light | button.outlined | light | focusvisible | text | ambient | 1.23 | 4.5 | canvas-identity-tone-light |
| light | button.outlined | light | focusvisible | border | ambient | 1.23 | 3 | canvas-identity-tone-light |
| light | button.outlined | light | selected | border | ambient | 1.10 | 3 | canvas-identity-tone-light |
| light | button.flat | surface | base | text | ambient | 1.00 | 4.5 | canvas-identity-tone-surface |
| light | button.flat | surface | hover | text | ambient | 3.13 | 4.5 | canvas-identity-tone-surface |
| light | button.flat | surface | focusvisible | text | ambient | 3.13 | 4.5 | canvas-identity-tone-surface |
| light | button.flat | surface | selected | text | ambient | 3.13 | 4.5 | canvas-identity-tone-surface |
| light | button.flat | light | base | text | ambient | 1.10 | 4.5 | canvas-identity-tone-light |
| light | button.flat | light | hover | text | ambient | 1.23 | 4.5 | canvas-identity-tone-light |
| light | button.flat | light | focusvisible | text | ambient | 1.23 | 4.5 | canvas-identity-tone-light |
| light | button.flat | light | selected | text | ambient | 1.23 | 4.5 | canvas-identity-tone-light |
| light | input.contained | surface | focus | border | ambient | 1.00 | 3 | canvas-identity-tone-surface |
| light | input.contained | light | focus | border | ambient | 1.10 | 3 | canvas-identity-tone-light |
| light | input.outlined | surface | base | text | ambient | 1.00 | 4.5 | canvas-identity-tone-surface |
| light | input.outlined | surface | base | border | ambient | 1.00 | 3 | canvas-identity-tone-surface |
| light | input.outlined | surface | focus | text | ambient | 1.00 | 4.5 | canvas-identity-tone-surface |
| light | input.outlined | surface | focus | border | ambient | 1.00 | 3 | canvas-identity-tone-surface |
| light | input.outlined | surface | invalid | text | ambient | 1.00 | 4.5 | canvas-identity-tone-surface |
| light | input.outlined | light | base | text | ambient | 1.10 | 4.5 | canvas-identity-tone-light |
| light | input.outlined | light | base | border | ambient | 1.10 | 3 | canvas-identity-tone-light |
| light | input.outlined | light | focus | text | ambient | 1.10 | 4.5 | canvas-identity-tone-light |
| light | input.outlined | light | focus | border | ambient | 1.10 | 3 | canvas-identity-tone-light |
| light | input.outlined | light | invalid | text | ambient | 1.10 | 4.5 | canvas-identity-tone-light |
| light | input.underline | surface | base | text | ambient | 1.00 | 4.5 | canvas-identity-tone-surface |
| light | input.underline | surface | focus | text | ambient | 1.00 | 4.5 | canvas-identity-tone-surface |
| light | input.underline | surface | invalid | text | ambient | 1.00 | 4.5 | canvas-identity-tone-surface |
| light | input.underline | light | base | text | ambient | 1.10 | 4.5 | canvas-identity-tone-light |
| light | input.underline | light | focus | text | ambient | 1.10 | 4.5 | canvas-identity-tone-light |
| light | input.underline | light | invalid | text | ambient | 1.10 | 4.5 | canvas-identity-tone-light |
| dark | surface.outlined | surface | base | text | ambient | 1.00 | 4.5 | canvas-identity-tone-surface |
| dark | surface.outlined | surface | base | border | ambient | 1.00 | 3 | canvas-identity-tone-surface |
| dark | surface.outlined | dark | base | text | ambient | 1.04 | 4.5 | canvas-identity-tone-dark |
| dark | surface.outlined | dark | base | border | ambient | 1.04 | 3 | canvas-identity-tone-dark |
| dark | surface.outlined | light | base | text | ambient | 1.38 | 4.5 | canvas-identity-tone-light |
| dark | surface.outlined | light | base | border | ambient | 1.38 | 3 | canvas-identity-tone-light |
| dark | surface.flat | surface | base | text | ambient | 1.00 | 4.5 | canvas-identity-tone-surface |
| dark | surface.flat | dark | base | text | ambient | 1.04 | 4.5 | canvas-identity-tone-dark |
| dark | surface.flat | light | base | text | ambient | 1.38 | 4.5 | canvas-identity-tone-light |
| dark | button.outlined | surface | base | text | ambient | 1.00 | 4.5 | canvas-identity-tone-surface |
| dark | button.outlined | surface | base | border | ambient | 1.00 | 3 | canvas-identity-tone-surface |
| dark | button.outlined | surface | hover | text | ambient | 3.21 | 4.5 | canvas-identity-tone-surface |
| dark | button.outlined | surface | focusvisible | text | ambient | 3.21 | 4.5 | canvas-identity-tone-surface |
| dark | button.outlined | surface | selected | border | ambient | 1.00 | 3 | canvas-identity-tone-surface |
| dark | button.outlined | dark | base | text | ambient | 1.04 | 4.5 | canvas-identity-tone-dark |
| dark | button.outlined | dark | base | border | ambient | 1.04 | 3 | canvas-identity-tone-dark |
| dark | button.outlined | dark | hover | text | ambient | 1.00 | 4.5 | canvas-identity-tone-dark |
| dark | button.outlined | dark | hover | border | ambient | 1.00 | 3 | canvas-identity-tone-dark |
| dark | button.outlined | dark | focusvisible | text | ambient | 1.00 | 4.5 | canvas-identity-tone-dark |
| dark | button.outlined | dark | focusvisible | border | ambient | 1.00 | 3 | canvas-identity-tone-dark |
| dark | button.outlined | dark | selected | border | ambient | 1.04 | 3 | canvas-identity-tone-dark |
| dark | button.outlined | light | base | text | ambient | 1.38 | 4.5 | canvas-identity-tone-light |
| dark | button.outlined | light | base | border | ambient | 1.38 | 3 | canvas-identity-tone-light |
| dark | button.outlined | light | hover | text | ambient | 1.95 | 4.5 | canvas-identity-tone-light |
| dark | button.outlined | light | hover | border | ambient | 1.95 | 3 | canvas-identity-tone-light |
| dark | button.outlined | light | focusvisible | text | ambient | 1.95 | 4.5 | canvas-identity-tone-light |
| dark | button.outlined | light | focusvisible | border | ambient | 1.95 | 3 | canvas-identity-tone-light |
| dark | button.outlined | light | selected | border | ambient | 1.38 | 3 | canvas-identity-tone-light |
| dark | button.flat | surface | base | text | ambient | 1.00 | 4.5 | canvas-identity-tone-surface |
| dark | button.flat | surface | hover | text | ambient | 3.21 | 4.5 | canvas-identity-tone-surface |
| dark | button.flat | surface | focusvisible | text | ambient | 3.21 | 4.5 | canvas-identity-tone-surface |
| dark | button.flat | surface | selected | text | ambient | 3.21 | 4.5 | canvas-identity-tone-surface |
| dark | button.flat | dark | base | text | ambient | 1.04 | 4.5 | canvas-identity-tone-dark |
| dark | button.flat | dark | hover | text | ambient | 1.00 | 4.5 | canvas-identity-tone-dark |
| dark | button.flat | dark | focusvisible | text | ambient | 1.00 | 4.5 | canvas-identity-tone-dark |
| dark | button.flat | dark | selected | text | ambient | 1.00 | 4.5 | canvas-identity-tone-dark |
| dark | button.flat | light | base | text | ambient | 1.38 | 4.5 | canvas-identity-tone-light |
| dark | button.flat | light | hover | text | ambient | 1.95 | 4.5 | canvas-identity-tone-light |
| dark | button.flat | light | focusvisible | text | ambient | 1.95 | 4.5 | canvas-identity-tone-light |
| dark | button.flat | light | selected | text | ambient | 1.95 | 4.5 | canvas-identity-tone-light |
| dark | input.contained | surface | focus | border | ambient | 1.00 | 3 | canvas-identity-tone-surface |
| dark | input.contained | dark | focus | border | ambient | 1.04 | 3 | canvas-identity-tone-dark |
| dark | input.contained | light | focus | border | ambient | 1.38 | 3 | canvas-identity-tone-light |
| dark | input.outlined | surface | base | text | ambient | 1.00 | 4.5 | canvas-identity-tone-surface |
| dark | input.outlined | surface | base | border | ambient | 1.00 | 3 | canvas-identity-tone-surface |
| dark | input.outlined | surface | focus | text | ambient | 1.00 | 4.5 | canvas-identity-tone-surface |
| dark | input.outlined | surface | focus | border | ambient | 1.00 | 3 | canvas-identity-tone-surface |
| dark | input.outlined | surface | invalid | text | ambient | 1.00 | 4.5 | canvas-identity-tone-surface |
| dark | input.outlined | dark | base | text | ambient | 1.04 | 4.5 | canvas-identity-tone-dark |
| dark | input.outlined | dark | base | border | ambient | 1.04 | 3 | canvas-identity-tone-dark |
| dark | input.outlined | dark | focus | text | ambient | 1.04 | 4.5 | canvas-identity-tone-dark |
| dark | input.outlined | dark | focus | border | ambient | 1.04 | 3 | canvas-identity-tone-dark |
| dark | input.outlined | dark | invalid | text | ambient | 1.04 | 4.5 | canvas-identity-tone-dark |
| dark | input.outlined | light | base | text | ambient | 1.38 | 4.5 | canvas-identity-tone-light |
| dark | input.outlined | light | base | border | ambient | 1.38 | 3 | canvas-identity-tone-light |
| dark | input.outlined | light | focus | text | ambient | 1.38 | 4.5 | canvas-identity-tone-light |
| dark | input.outlined | light | focus | border | ambient | 1.38 | 3 | canvas-identity-tone-light |
| dark | input.outlined | light | invalid | text | ambient | 1.38 | 4.5 | canvas-identity-tone-light |
| dark | input.underline | surface | base | text | ambient | 1.00 | 4.5 | canvas-identity-tone-surface |
| dark | input.underline | surface | focus | text | ambient | 1.00 | 4.5 | canvas-identity-tone-surface |
| dark | input.underline | surface | invalid | text | ambient | 1.00 | 4.5 | canvas-identity-tone-surface |
| dark | input.underline | dark | base | text | ambient | 1.04 | 4.5 | canvas-identity-tone-dark |
| dark | input.underline | dark | focus | text | ambient | 1.04 | 4.5 | canvas-identity-tone-dark |
| dark | input.underline | dark | invalid | text | ambient | 1.04 | 4.5 | canvas-identity-tone-dark |
| dark | input.underline | light | base | text | ambient | 1.38 | 4.5 | canvas-identity-tone-light |
| dark | input.underline | light | focus | text | ambient | 1.38 | 4.5 | canvas-identity-tone-light |
| dark | input.underline | light | invalid | text | ambient | 1.38 | 4.5 | canvas-identity-tone-light |