Who it affects
Someone with low vision checking a gym's class timetable on their phone cannot read the pale grey room names and times, and turns up at the wrong studio.
Anyone outdoors in bright sunlight watches a white button on a light blue background disappear into the glare.
Someone in their seventies with the normal yellowing of the eye's lens finds light grey text on white far harder to read than it looked on the designer's monitor.
Broken versus fixed
Both examples are live and rendered in a sandboxed frame. The broken one is deliberately broken.
Room names in #9a9a9a on white measure 2.8 to 1. White button text on #5aa0e6 measures 2.8 to 1. Both fail the 4.5 to 1 minimum.
Text in #595959 on white measures 7.0 to 1. White on #1b5e9e measures 6.7 to 1. Both pass with room to spare.
Show the markup behind both examples
<style>body{font-family:system-ui;margin:0;padding:16px;background:#fff}table{border-collapse:collapse;font-size:15px}td{padding:4px 12px 4px 0;color:#9a9a9a}td:first-child{color:#1f2937;font-weight:600}.btn{display:inline-block;margin-top:10px;background:#5aa0e6;color:#fff;padding:8px 14px;border-radius:6px;font-size:15px}</style>
<table>
<tr><td>Spin</td><td>Studio 2</td><td>6.00pm</td></tr>
<tr><td>Yoga</td><td>Studio 1</td><td>7.00pm</td></tr>
<tr><td>Circuits</td><td>Main hall</td><td>7.30pm</td></tr>
</table>
<span class="btn">Book a class</span>
<style>body{font-family:system-ui;margin:0;padding:16px;background:#fff}table{border-collapse:collapse;font-size:15px}td{padding:4px 12px 4px 0;color:#595959}td:first-child{color:#1f2937;font-weight:600}.btn{display:inline-block;margin-top:10px;background:#1b5e9e;color:#fff;padding:8px 14px;border-radius:6px;font-size:15px}</style>
<table>
<tr><td>Spin</td><td>Studio 2</td><td>6.00pm</td></tr>
<tr><td>Yoga</td><td>Studio 1</td><td>7.00pm</td></tr>
<tr><td>Circuits</td><td>Main hall</td><td>7.30pm</td></tr>
</table>
<span class="btn">Book a class</span>
Test it by hand in two minutes
- Right click the text, choose Inspect, and in the Styles panel click the colour swatch next to color. Chrome shows the contrast ratio with a tick or a cross for AA.
- Note the font size. Below about 24px (or 19px bold) the target is 4.5 to 1; at or above that size, 3 to 1.
- Check text over photos, gradients and hover states too, where the background is not one flat colour.
- Turn the screen brightness down and look again. If you are squinting, so are your readers.
The fix
.timetable td { color: #9a9a9a; }
.timetable td { color: #595959; }
Darken the text or lighten the background until the ratio clears the target. The hue can stay if the brand matters; it is the brightness that counts.
Button text on a brand colour
.btn { background: #5aa0e6; color: #ffffff; }
.btn { background: #1b5e9e; color: #ffffff; }
White on the light brand blue was 2.8 to 1. The darker blue gives 6.7 to 1. A dark text colour on the light blue is the other option.
Placeholder text
input::placeholder { color: #a3a3a3; }
input::placeholder { color: #767676; }
Placeholders count as text: #a3a3a3 on white is 2.5 to 1, #767676 is 4.5 to 1. Better still, put the hint in a visible label.
Text over a photo
.hero h1 { color: #ffffff; }
.hero h1 { color: #ffffff; background: rgba(0, 0, 0, 0.6); padding: 4px 8px; }
A solid or dark translucent band behind the text makes the ratio predictable whatever the photo shows.
Common mistakes
- Checking the ratio in the design file, then shipping a lighter 'muted' grey for secondary text.
- Counting 16px semi-bold text as large; large means about 24px, or 19px bold.
- Fading text with opacity, which lowers the contrast by an amount nobody has measured.
- Fixing the body text and forgetting links, placeholders, buttons that look disabled but are not, and error messages in red.
- Relying on a colour blindness simulator; contrast is about brightness, and a simulator does not measure it.
WCAG criteria this rule maps to
axe-core tags this rule as WCAG 2.2 level AA. It is one of the checks for:
- 1.4.3 Contrast (Minimum) (level AA). Official W3C text.
The scanner works out the ratio between the text colour and the background it can resolve, allowing for font size and weight. It skips text over images, gradients and some overlapping layers and reports those for a manual check, so a clean scan does not mean every piece of text passes. Automated tools find only part of the accessibility problems on a page; a pass on this rule is not a pass on the criterion.
Related rules
link-in-text-block
When a link sits inside a paragraph, colour is often the only thing that marks it out.
AA
Buttons and links must be big enough to press
target-size
Every control you can click or tap needs to be at least 24 by 24 CSS pixels, or have enough empty space around it that a 24 pixel circle centred on it does not touch a neighbour.
AA
Text spacing must not be locked with !important
avoid-inline-spacing
Some people with dyslexia or low vision read far better with wider line, letter or word spacing, and they apply it with a browser extension or a bookmarklet.