Low contrast text: WCAG contrast ratios and how to fix them
What it means
Contrast is the difference in brightness between text and the background behind it. WCAG, the web accessibility standard, expresses it as a ratio from 1:1 (identical colours) to 21:1 (black on white). MobileCheck flags text that falls below the WCAG AA level:
- 4.5:1 for normal text.
- 3:1 for large text, defined as at least 24px, or at least 18.66px when bold. (The spec says 18pt and 14pt bold; those are the CSS pixel equivalents.)
The stricter AAA level asks for 7:1 and 4.5:1. AA is the level most laws and policies refer to and the one MobileCheck uses.
Why it matters
Low contrast is a problem for everyone on a phone, not only for people with low vision. Phones are used outdoors, at low brightness to save battery, through screen glare, and on displays that don't reproduce subtle greys well. Light-grey text that looks elegant on a calibrated monitor can simply vanish in sunlight.
Contrast also comes up in accessibility audits more than almost any other issue, and it is one of the easiest to fix: usually a colour value or two in your stylesheet.
How the contrast ratio is calculated
The ratio compares the relative luminance (perceived brightness) of the two colours: (lighter + 0.05) / (darker + 0.05). Hue doesn't count; only brightness does. That is why red text on green can fail even though the colours look very different.
Some reference points on a white background:
| Text colour | Ratio on #ffffff | Normal text (4.5:1) |
|---|---|---|
#000000 | 21:1 | Passes |
#595959 | about 7:1 | Passes (AAA too) |
#767676 | about 4.5:1 | Just passes |
#777777 | about 4.48:1 | Just fails |
#999999 | about 2.8:1 | Fails |
You don't need to calculate this by hand. Chrome DevTools shows the contrast ratio when you click a colour swatch for text in the Styles panel, and Lighthouse includes a contrast audit. Any online colour contrast checker will test a pair for you.
How MobileCheck finds it
MobileCheck reads the computed text colour of each visible text element and works out the effective background by walking up through its ancestors, blending semi-transparent layers. It then applies the 4.5:1 or 3:1 threshold depending on the text's size and weight, and re-checks each candidate before reporting it. Failing elements are highlighted on the screenshot.
The check runs three times per device: in the normal view, with dark mode turned on (prefers-color-scheme: dark), and with the mobile menu opened. Text drawn over a background image or gradient is skipped, because its contrast changes from pixel to pixel and can't be measured reliably; check those spots by eye.
How to fix it
Darken the text or lighten the background
Most fixes are a single value. If your grey body text is #999, moving to #595959 or #666 keeps the soft look and passes comfortably:
body { color: #222; background: #fff; }
.muted { color: #595959; } /* about 7:1 on white */
a { color: #0b57d0; } /* check brand colours too */
Brand colours are a frequent cause. A bright brand orange or light blue often works for buttons with dark text, or as a background, but fails as text on white. Keep the brand colour and pick a darker shade for text.
Large text
Headings and big buttons can use slightly lighter colours because the 3:1 threshold applies to them. Remember that "large" means 24px or 18.66px bold in CSS pixels. A 20px regular heading still needs 4.5:1.
Placeholder text
Placeholder text inside form fields is text, and WCAG's contrast guidance applies to it. Many designs use very pale placeholders. Darken them, and better still, put essential hints in a visible label instead of a placeholder that disappears when the user starts typing:
::placeholder { color: #6b6b6b; opacity: 1; }
Text in disabled controls and in logos is exempt from the contrast requirement.
Text on images
Text over photos may pass on one part of the image and fail on another, and phones crop hero images differently from desktops. Give the text a guaranteed background: a semi-transparent overlay, a gradient behind the text, or a solid panel:
.hero { position: relative; }
.hero::before {
content: ""; position: absolute; inset: 0;
background: linear-gradient(to top, rgb(0 0 0 / 0.65), transparent 60%);
}
.hero-text { position: relative; color: #fff; }
Dark mode
Dark mode is where contrast problems hide, because few people test it. Common causes:
- The page declares
color-scheme: light dark(or the matching meta tag), so the browser paints a dark default background, but some text still has a hard-coded dark colour. - A dark-mode stylesheet changes the main background but misses cards, menus, tables or form fields.
- Mid-grey text that passed on white now sits on dark grey and fails.
Define colours as custom properties and switch them together, so text and background always change as a pair:
:root { --bg: #fff; --text: #222; --muted: #595959; }
@media (prefers-color-scheme: dark) {
:root { --bg: #121212; --text: #eaeaea; --muted: #a8a8a8; }
}
body { background: var(--bg); color: var(--text); }
The dark mode guide covers this in more detail.
How to check the fix
Run the free check again and compare the normal and dark-mode results; both should be free of contrast warnings. Then open the page in the phone window and look at areas MobileCheck can't measure, such as text over photos and sliders.
Fonts render differently in WebKit and Chromium, and thin weights can look lighter in one than the other. The ratio is the same, but thin text at a borderline ratio reads worse, so leave a margin above the minimum. If text is also small, fixing text size at the same time makes the biggest difference.
Frequently asked questions
What contrast ratio does WCAG require?
WCAG 2.2 level AA requires at least 4.5:1 for normal text and 3:1 for large text. Level AAA raises this to 7:1 and 4.5:1.
What counts as large text for contrast?
Text of at least 18pt, or at least 14pt when bold. In CSS pixels that is 24px regular or about 18.66px bold.
Does placeholder text need to meet contrast requirements?
Yes, placeholder text is still text people need to read, so it should meet the same ratio. Text in disabled controls and in logos is exempt.
Why does my site fail contrast only in dark mode?
Usually some text keeps a hard-coded dark colour while the background turns dark, or a dark theme misses some components. Switch text and background colours together, for example with CSS custom properties.