WCAG Color Contrast Ratio: How to Check and Fix It
Learn how the WCAG contrast ratio is calculated from relative luminance, the AA and AAA thresholds for text and UI components, and how to fix a failing color pair.
WCAG requires a contrast ratio of at least 4.5:1 for normal text and 3:1 for large text at Level AA, rising to 7:1 and 4.5:1 at Level AAA. The ratio compares the relative luminance of two colors as (L1 + 0.05) / (L2 + 0.05), so it runs from 1:1 for identical colors to 21:1 for black on white.
WCAG contrast ratio requirements
| What you are checking | Success criterion | Minimum ratio |
|---|---|---|
| Normal text, AA | 1.4.3 | 4.5 : 1 |
| Large text, AA | 1.4.3 | 3 : 1 |
| Normal text, AAA | 1.4.6 | 7 : 1 |
| Large text, AAA | 1.4.6 | 4.5 : 1 |
| UI components and graphics, AA | 1.4.11 | 3 : 1 |
The UI component rule covers things like input borders, focus rings, toggle states and icons that carry meaning, measured against the colors next to them. These thresholds are the same in WCAG 2.1 and 2.2.
What counts as large text
WCAG treats text as large when it is at least 18 point, or at least 14 point and bold. A CSS point is 4/3 of a pixel, so that is about 24px at regular weight or about 18.66px bold. Anything smaller is normal text and needs the higher ratio.
The checker's sample heading is labelled Large Text and its body paragraph Normal Text, but it does not measure the sizes you actually use. Read the Large badges only if your text meets the size rule above.
How the contrast ratio is calculated
The calculation has three steps, applied to each of the two colors.
- Divide each red, green and blue value by 255 to get
c. - Linearize it: if
c ≤ 0.04045, usec / 12.92; otherwise use((c + 0.055) / 1.055) ^ 2.4. - Weight the channels:
L = 0.2126 R + 0.7152 G + 0.0722 B.
Then divide: (L1 + 0.05) / (L2 + 0.05), where L1 is the lighter color. Green carries the most weight, which is why a bright green can look light but a pure blue of the same strength fails.
Older versions of the WCAG text give the cutoff as 0.03928. W3C changed it to 0.04045 in 2021 and says this has no practical effect, because no 8-bit color value falls between the two numbers.
const toLinear = (v) => {
const c = v / 255;
return c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
};
const luminance = ([r, g, b]) =>
0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b);
const contrast = (a, b) => {
const [hi, lo] = [luminance(a), luminance(b)].sort((x, y) => y - x);
return (hi + 0.05) / (lo + 0.05);
};Worked example: gray text on white
Take #777777 text on a white background. All three channels are 119, so the weights add up to 1 and L equals the linearized value.
| Step | #777777 | #FFFFFF |
|---|---|---|
| c = value / 255 | 0.4667 | 1 |
| Linearized | 0.1845 | 1 |
| L | 0.1845 | 1 |
The ratio is (1 + 0.05) / (0.1845 + 0.05) = 1.05 / 0.2345 ≈ 4.478. That is just under 4.5, so it fails Normal AA but passes Large AA. Most tools round this to 4.48. The Color Converter & Contrast Checker truncates to two decimals instead, so it shows 4.47, and a pair that misses a threshold never reads as if it met it.
Nudging the gray to #767676 gives L ≈ 0.1812 and a ratio of about 4.54, which passes Normal AA. It is the lightest pure gray that does on white.
How to check contrast step by step
Enter the text and background colors
In the Color Converter & Contrast Checker, type the text color into Text / Foreground and the background into Surface / Background. HEX, rgb() and hsl() values all work, with or without alpha.
Read the contrast ratio
The checker shows the ratio as x : 1, along with the relative luminance of each color. The ratio runs from 1 : 1 for identical colors to 21 : 1 for black on white.
Match the ratio to your text size
Check the four badges: Normal AA needs 4.5, Normal AAA 7.0, Large AA 3.0 and Large AAA 4.5. Pick the row that matches your text size; the checker does not measure your real font sizes.
Fix a failing pair
Change the lightness of one color, not its hue, then test again. Type a new value into the color input, then use Set as FG or Set as BG to move it into the checker, or use Swap colors to test the reverse pairing.
Check icons, borders and focus rings against 3:1
WCAG asks for at least 3:1 for UI components and graphics. The checker has no badge for this, so compare the displayed ratio with 3 yourself.
How to fix a pair that fails
For opaque colors that fail AA for normal text, choose “Suggest accessible foreground” to preview a passing alternative. It adjusts HSL lightness while keeping hue, saturation, and the background fixed. Choose “Apply” to update the foreground; neither input changes before then. Suggestions are unavailable for transparent colors. You can also make these manual adjustments:
- Change lightness, not hue. Darken the text on a light background, or lighten it on a dark one. Typing
hsl()with a lower or higher lightness keeps the color family intact. - Change the background instead when the text color is fixed, such as a brand color.
- Make the text larger or bold if it qualifies as large text, which lowers the minimum from 4.5:1 to 3:1.
- Use an opaque color in place of a semi-transparent one, so the ratio does not depend on what sits behind it.
- Test the reverse pairing with Swap colors, which is useful when the same two colors appear on both light and dark surfaces.
After each change, type the new value in the color input and use Set as FG or Set as BG to send it to the checker, then read the new ratio.
Check your colors in the browser
The Color Converter & Contrast Checker accepts HEX (3, 4, 6 or 8 digits), rgb() and hsl() values, including the space and slash syntax. It shows the ratio, each color's relative luminance and four pass or fail badges, and previews your colors on a sample heading and paragraph. It runs in your browser, so nothing is uploaded.
// Frequently Asked Questions
What is a good contrast ratio for text?
For WCAG Level AA, normal text needs at least 4.5:1 and large text at least 3:1. Level AAA raises these to 7:1 and 4.5:1. Higher is easier to read, and 21:1 (black on white) is the maximum.
How is the WCAG contrast ratio calculated?
Each color is converted to a relative luminance between 0 and 1, then the ratio is (L1 + 0.05) / (L2 + 0.05), where L1 is the lighter color. Luminance weights linearized red, green and blue as 0.2126, 0.7152 and 0.0722.
What counts as large text in WCAG?
Text that is at least 18 point, or at least 14 point if bold. In CSS pixels that is about 24px regular or about 18.66px bold. Anything smaller is normal text.
Do icons, borders and focus rings need contrast too?
Yes. WCAG 2.1 success criterion 1.4.11 requires at least 3:1 against adjacent colors for UI components and the graphics needed to understand content. The CODERCOPS checker reports text ratings only, so compare its ratio with 3:1 for these.
Does the checker handle transparent colors?
Yes. A translucent background is composited over white, and a translucent foreground is composited over that background, before the ratio is calculated. Use an opaque color if you want to test a specific rendered result.
Why does the checker show 4.47 when other tools show 4.48?
The checker truncates the ratio to two decimals instead of rounding up, so a pair that misses a threshold never displays as if it met it. A ratio of 4.478 therefore reads 4.47, and the Normal AA badge shows FAIL.