WCAG contrast checker

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

WCAG Colour Contrast Checker

Check if your text and background colour combination meets WCAG 2.1 accessibility standards for AA and AAA compliance.

12.63
: 1 Contrast Ratio
AA Normal Text (≥ 4.5:1) PASS ✓
AA Large Text (≥ 3:1) PASS ✓
AAA Normal Text (≥ 7:1) PASS ✓
AAA Large Text (≥ 4.5:1) PASS ✓

Live Text Preview

Heading Text Sample (24px)

This is what your body text will look like with these colour combinations. Check if it's easily readable at this size.

Small text (12px) is harder to read and needs higher contrast ratios to pass WCAG compliance.

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 virtually all legislation references — the UK Equality Act, the European Accessibility Act, 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, under WCAG 2.1, a 3:1 minimum 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 — the default grey in most browsers fails badly, and 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 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.

Developer Implementation Guide: Calculating Contrast in JS

If you are building dynamic themes where users can select their own brand colors, you must programmatically ensure that text placed over those colors remains readable. The World Wide Web Consortium (WCAG) provides a specific mathematical formula for calculating this relative luminance.

The WCAG Contrast Algorithm

Here is the implementation in JavaScript to calculate the contrast ratio between two hex colors, allowing you to automatically switch text between black and white depending on the background.


// Helper: Convert Hex to RGB, apply sRGB gamma correction, and calculate luminance
function getLuminance(hex) {
  const rgb = hex.match(/[A-Za-z0-9]{2}/g).map(v => parseInt(v, 16) / 255);
  const a = rgb.map(v => {
    return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
  });
  // Standard WCAG luminance formula
  return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722;
}

// Main function: Calculate ratio between two colors
function getContrastRatio(hex1, hex2) {
  const lum1 = getLuminance(hex1);
  const lum2 = getLuminance(hex2);
  
  const brightest = Math.max(lum1, lum2);
  const darkest = Math.min(lum1, lum2);
  
  return (brightest + 0.05) / (darkest + 0.05);
}

// Usage: Should we use white or black text on a blue background?
const bgLuminance = getLuminance("#007bff");
const whiteRatio = getContrastRatio("#007bff", "#ffffff");
const blackRatio = getContrastRatio("#007bff", "#000000");

const textColor = whiteRatio > blackRatio ? "#ffffff" : "#000000";
        

Integrating this logic into your application ensures you never fail accessibility audits due to user-generated color themes. A ratio of 4.5:1 is the minimum target for standard body text (WCAG AA).

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.1 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. Default browser placeholder grey fails, which is one of the most common accessibility issues 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.