Where 4.5:1 comes from
It is the line at which text stays readable at 20/40 vision — the level where things look blurred even with correction. Contrast sensitivity drops with age, so for any service with users over forty this effectively covers everyone.
| Applies to | AA (minimum) | AAA (recommended) |
|---|---|---|
| Body text | 4.5:1 | 7:1 |
| Large text | 3:1 | 4.5:1 |
| Button borders, icons, input outlines | 3:1 | — |
| Logos, disabled elements | No requirement | — |
Large text means 18.66px (14pt) bold or 24px (18pt) and above. It is decided by the pixel value, not by "it is a heading, so it is large".
Grey text fails most often
#999 on white is 2.85:1 — a fail. It looks elegant in the mockup and a good
portion of your users cannot read it. The lightest grey usable for body text on white is
#767676 (4.54:1).
When it fails, you get a colour
Stopping at "this does not meet the threshold" just leads to darkening something at random. So the nearest passing colour comes with it. Only the lightness in OKLCH is moved, so hue and saturation survive — your blue button does not turn grey.
Some colours cannot be fixed by lightness alone (white text on fluorescent yellow). For those, the search continues while lowering saturation.
Contrast is not everything
These thresholds are a floor. Passing does not mean it reads well. White on black scores a perfect 21:1 and tires the eye quickly over a long article; body text is usually most comfortable around 12:1.
WCAG 2's formula has long been criticised for diverging from perception on dark backgrounds, which is why a new method called APCA is under discussion as a WCAG 3 candidate. What is still required legally and contractually today is WCAG 2, so that is what this tool measures.
Why only two colours? Real screens are more complicated
Contrast is a property of two adjacent colours, so measuring it always means narrowing to a pair. For text over an image, take the brightest and the darkest point the text crosses and measure twice. Both have to pass for it to be safe.
How are transparent colours handled?
The background is composited over white and the text colour is composited over that
background, producing the opaque colours you actually see. You can paste something like
rgba(0 0 0 / 0.6) straight in.
How do I check many colours at once?
Paste the whole palette into Colour Code Converter and each line tells you whether white or black text is readable on that colour.