Colour Contrast
Date: 2026-08-17
The luminance difference between foreground and background, expressed as a ratio. It’s the most commonly failed accessibility requirement, it’s entirely computable, and it affects far more people than any other single criterion.
Contrast ratio compares the relative luminance of two colours, from 1:1 (identical) to 21:1 (black on white).
The thresholds
NORMAL TEXT 4.5:1 AA
7:1 AAA
LARGE TEXT 3:1 AA
≥18pt, or ≥14pt bold 4.5:1 AAA
UI COMPONENTS AND 3:1 AA
GRAPHICS
borders, icons,
focus indicators,
chart elements
DECORATIVE / DISABLED no requirement
“Large text” is bigger than people assume — roughly 24px, or 18.66px bold, in CSS pixels. Body copy at 16px never qualifies.
[CHECK: the exact WCAG size thresholds and the current status of any newer contrast algorithm before treating a borderline value as passing.]
Who it affects
Far more people than “visually impaired” suggests:
low vision
colour vision deficiency
→ roughly 1 in 12 men
age-related contrast sensitivity loss
→ everyone, eventually
bright sunlight on a phone
cheap or dimmed screens
tired eyes
Contrast is the accessibility criterion with the widest ordinary benefit. Almost everyone reading a phone outdoors is temporarily in the affected group.
Build it into the palette
The approach that makes this a solved problem rather than a recurring audit:
on white
blue.400 2.6:1 ✗ fails body text
blue.500 3.9:1 ✗ fails body, ok for UI
blue.600 5.2:1 ✓
blue.700 7.4:1 ✓ AAA
Publish that table with the palette. A colour scale with no contrast figures guarantees someone uses blue.400 for body text — and the failure won’t be noticed by anyone who can read it — Colour Systems, Design Tokens.
Never colour alone
A separate requirement, and separately failed:
FAILS
a red border as the only error
indicator
green "in stock" / red "out of
stock" with no text
a chart distinguished only by
colour
links coloured but not underlined
PASSES
colour PLUS an icon
colour PLUS text
colour PLUS underline or shape
Test by taking a greyscale screenshot. Anything that becomes ambiguous is failing — Affordances and Signifiers.
Where it’s commonly failed
PLACEHOLDER TEXT grey on white,
almost always failing
— another reason not
to use placeholders
as labels
— Form Design
DISABLED STATES exempt from the
requirement, and often
unreadable
— Component States
TEXT ON IMAGES contrast varies across
the image
→ use a scrim or a
solid panel
BRAND COLOURS a brand accent that
fails on white is a
real conflict, and
"it's our brand" is
not an exemption
LIGHT GREY BODY TEXT #999 on white is
2.8:1 and extremely
common
FOCUS INDICATORS need 3:1 against BOTH
the component and its
background
— Focus Management
See: Form Design · Component States · Focus Management
#999 on white is the single most common contrast failure on the web, and it’s usually a deliberate aesthetic choice to make secondary text look secondary.
Dark mode needs rechecking
Contrast doesn’t transfer between themes:
LIGHT blue.600 on white 5.2:1 ✓
DARK blue.600 on #16181c 2.9:1 ✗
→ needs blue.400 in dark
Every token pair must be checked in every theme. This is why the semantic layer matters — the theme swaps the value, and the check has to swap with it — Theming.
Tooling
DevTools contrast shown in the
colour picker and the
accessibility panel
automated scans catch text contrast
reliably
← one of the things
automation IS good at
design tools contrast plugins
CI enforce it on the token
set, not per component
Contrast is one of the few accessibility criteria automation catches well. There is no good reason to ship a failure — WCAG.