Contrast contract
This is a spec: descriptive guidance, not a checkable rule. It carries no forbidden and instead pair, and no jig check detector applies to it directly.
The spec
Floor: WCAG 2.1 AA. Two thresholds, and the boundary between them is a common mistake.
| Threshold | |
|---|---|
| Small text — 18px or less | 4.5:1 |
| Large text — 24px+ regular, or 18px+ bold | 3:1 |
| UI elements — input, checkbox and radio borders; meaningful icons | 3:1 |
| Decorative elements that convey no meaning | none |
Test against fill, not against the background. Text and controls can sit on a fill surface (a tag, a badge, a highlighted row), which is the lowest-contrast case. A colour that passes on the page background can fail inside a tag. In dark mode, test against bg-overlay — the brightest level, and therefore the worst case for a light-on-dark foreground.
The brand colour needs 4.5:1 against both bg-raised and fill, because it is used for link and button text.
When the brand colour cannot reach 4.5:1 — a yellow or very light brand, or a dark brand on a dark surface:
- 1.Darken or lighten it slightly, if brand recognition survives.
- 2.Use
--color-text-strongfor interactive elements instead, and keep the brand colour decorative. - 3.Add a border to buttons so they clear 3:1.
APCA
WCAG 2's algorithm has known failures — it will pass black text on orange and fail white text on the same orange, when the white is plainly more readable, and it works poorly on dark interfaces. APCA (WCAG 3 draft) measures perceptually and scores by size and weight rather than a flat ratio.
Guidance: for commercial work, comply with WCAG 2.1 AA, because that is what is legally referenced. Check APCA as well, particularly on dark surfaces. Aim to pass both.
APCA reference values, with the sizes they apply at — a score means nothing without one, since APCA takes size and weight into account and thin or small text scores lower for the same colours:
| Score | Applies to |
|---|---|
| 90 | Preferred for body text, 14px regular and above |
| 75 | Minimum for body text, 18px regular and above |
| 60 | Minimum for other text, 24px regular or 16px bold and above |
| 45 | Minimum for large text — 36px regular or 24px bold and above — and for interface elements |
| 30 | Absolute minimum for text: placeholders, disabled button text |
| 15 | Minimum for non-text elements |
These thresholds are APCA's own and do not line up with WCAG's large-text definition (C-17, 24px regular / 18.66px bold) — the two systems measure differently, and each is right inside its own frame.
- Kind
- Spec
- Section
- Design Tokens