---
title: "WCAG Colour Contrast Checker (AA & AAA)"
description: "Free WCAG color contrast checker. Test text and background combinations against AA/AAA standards with live previews and instant pass/fail results."
url: https://frontendhelpers.com/contrast-checker
updated: 2026-10-12
---

# WCAG contrast checker

Test any text and background pairing against WCAG 2.2 AA and AAA, for both normal and large text, with a live preview of the result.

## How to use this tool

Contrast ratio compares the relative luminance — the perceived brightness — of two colours. It runs from 1:1 (identical, invisible) to 21:1 (pure black on pure white). The calculation is defined by WCAG and weights the red, green and blue channels differently, because human eyes are far more sensitive to green than to blue.

That weighting is why intuition misleads people. A mid-yellow and a mid-blue can look equally "medium" while having wildly different luminance, and two colours that feel high-contrast to you may fail outright. Measuring is not optional.

## The four thresholds, and which one applies

**AA** is the level that legislation and procurement rules point to — the UK's public sector accessibility regulations, the European Accessibility Act (through the harmonised standard EN 301 549) and Section 508 in the US. It requires **4.5:1** for normal text and **3:1** for large text. **AAA** is the enhanced level, requiring **7:1** and **4.5:1** respectively.

"Large text" has a specific definition: at least **24px**, or **18.66px if bold**. It gets a lower threshold because thicker letterforms remain legible at lower contrast. Note that the requirement covers text and images of text — plus, since WCAG 2.1 and still in the current WCAG 2.2, a 3:1 minimum (success criterion 1.4.11) for meaningful UI components and graphics such as input borders, icons and focus indicators.

## The mistakes that fail real audits

Three patterns account for most failures. **Placeholder text** is the worst offender — placeholder greys are pale by design, some browser defaults fall short, and many design systems lighten them further. Placeholders are frequently misused as labels, so the information disappears for low-vision users. **Light grey body text** on white is the second: it looks refined in a design mockup on a bright calibrated monitor, and becomes unreadable on a phone in daylight. **Text over images or gradients** is the third, because the ratio changes across the background — check the lightest and darkest points it crosses, not the average, and add a scrim if either end fails.

Also remember that disabled controls are exempt from the contrast requirement, but "looks disabled" is not the same as being disabled. If a control is interactive, it must meet the threshold.

## Designing for contrast from the start

Retrofitting contrast is painful; building it in is nearly free. Generate your palette as a tint-and-shade scale and record which steps are text-safe on which backgrounds — as a rule of thumb, steps around 600 and darker work for text on white, and 200 and lighter work as backgrounds for dark text. Our [palette generator](https://frontendhelpers.com/color-palette-generator) produces exactly that kind of scale.

One caution about automated testing: tools like Lighthouse and axe catch a meaningful share of contrast problems, but they cannot evaluate text over images, canvas or video, and they miss states they never trigger — hover, focus, error. Manual spot-checks on those states remain necessary.

## Picking black or white text automatically

If users can choose their own brand or theme colours, your code has to choose the text colour. The WCAG 2.2 formula is short, but copies of it around the web often break in two ways: the hex parser only understands six-digit colours, so `#777` produces `NaN`, and the regex accepts characters that are not hex at all, which `parseInt` silently misreads. This version validates its input, expands `#rgb` shorthand, and uses the 0.04045 threshold from the current specification. Older copies use 0.03928; for 8-bit colours the result is identical, because no 8-bit channel value falls between the two.

```javascript
function parseHex(hex) {
  const m = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(hex.trim());
  if (!m) throw new Error(`Not a hex colour: ${hex}`);
  const h = m[1].length === 3 ? m[1].replace(/./g, '$&$&') : m[1];
  return [0, 2, 4].map(i => parseInt(h.slice(i, i + 2), 16) / 255);
}

// WCAG 2.2 relative luminance (note the 0.04045 threshold)
function luminance(hex) {
  const [r, g, b] = parseHex(hex).map(c =>
    c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4);
  return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}

function contrast(a, b) {
  const [hi, lo] = [luminance(a), luminance(b)].sort((x, y) => y - x);
  return (hi + 0.05) / (lo + 0.05);
}

// Whichever of black or white contrasts more with the background
const textOn = bg =>
  contrast(bg, '#fff') >= contrast(bg, '#000') ? '#fff' : '#000';

console.log(contrast('#007bff', '#fff').toFixed(2)); // 3.98 – fails AA body text
console.log(contrast('#007bff', '#000').toFixed(2)); // 5.28 – passes
console.log(textOn('#007bff'), textOn('#333'));      // #000 #fff
console.log(contrast('#777', '#fff').toFixed(2));    // 4.48 – #rgb works
```

Two results surprise people. First, `#007bff`, a very common button blue, is more readable with black text than white: 5.28:1 against 3.98:1, and white fails AA for body text. Second, if you pick whichever of black or white scores higher, the result is never below 4.58:1 for any background colour, so the rule always passes AA for normal text. It does not guarantee AAA's 7:1. For that you have to adjust the background itself.

The maths is in the [WCAG 2.2 definition of relative luminance](https://www.w3.org/TR/WCAG22/#dfn-relative-luminance), and the reasoning behind the thresholds is in the W3C's [Understanding SC 1.4.3: Contrast (Minimum)](https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html).

## Frequently asked questions

### What contrast ratio do I need to pass WCAG?

For AA — the level most laws reference — you need 4.5:1 for normal text and 3:1 for large text. For AAA it is 7:1 and 4.5:1. WCAG 2.2 also requires 3:1 for meaningful UI components such as input borders and focus indicators.

### What counts as "large text" in WCAG?

Text of at least 24px, or 18.66px if bold. Larger and heavier letterforms stay legible at lower contrast, so they get the more relaxed 3:1 threshold instead of 4.5:1.

### Does placeholder text need to pass contrast?

Yes. Placeholder text is real text and must meet the same 4.5:1 minimum. Pale placeholder grey is one of the most common contrast failures on the web — and placeholders should never replace a visible label.

### How do I check contrast for text over an image?

Check the text against the lightest and the darkest point of the background it crosses, not an average. If either fails, add a semi-transparent scrim between the image and the text, or apply a text shadow, until both extremes pass.

## Related tools

- [Colour Palette Generator](https://frontendhelpers.com/color-palette-generator): Build a tint and shade scale where you know which steps are text-safe.
 
- [PX to REM Converter](https://frontendhelpers.com/px-to-rem-converter): The other half of accessible typography — text that scales with user settings.
 
- [WCAG 2.2 colour contrast, explained](https://frontendhelpers.com/wcag-colour-contrast-guide): Large text, non-text contrast, focus rings, transparency and 17 real colour pairs tested.

---

This page is a free interactive tool. Use it in your browser at https://frontendhelpers.com/contrast-checker
