Skip to content

Free tool - no signup

WCAG Contrast Checker

Pick a text color and a background, and see the live ratio with AA and AAA pass or fail badges for normal text, large text, and UI components. Nothing leaves your browser.

Large heading text

The quick brown fox jumps over the lazy dog. This is body copy at a normal reading size.

Button label
--contrast ratio
  • AA Normal text 4.5:1
  • AAA Normal text 7:1
  • AA Large text 3:1
  • AAA Large text 4.5:1
  • AA UI & graphics 3:1

How to use it

  1. Set your text color and background with the pickers or by typing a hex value.
  2. Read the ratio, then check the badges. Green means it passes that level.
  3. Large text is 24px regular or 18.66px bold and up, so headings clear a lower bar than body copy.
  4. Use Swap colors to test the same pair as light-on-dark and dark-on-light.

Why 4.5:1 is the line that matters

Most failing designs miss on body text, not headings. A soft grey on white looks refined on a calibrated monitor and then vanishes on a phone in daylight. WCAG AA asks for 4.5:1 on normal text for a reason: it keeps the text legible across the messy real-world conditions your visitors actually read in. Hit AA before you ship, and treat AAA as the target for anything people read for long stretches.

Fixing a real failing pair, step by step

Here's one I fixed last spring. The brand color was a warm mid grey, #8b8b83, used for secondary body copy on an off-white background of #fdfcf9. Paste those two into the tool and you get 2.89:1. That fails AA for normal text, fails AA for large text, and fails the 3:1 bar for UI components too. It had been live for two years and shipped in the design system as a token named text-secondary.

First move: darken the foreground and leave the background alone, because the background was doing real work as a warm paper tone across the whole product. Dropping to #6f6f66 gets 4.14:1. Closer, still short. Dropping again to #5e5e55 lands at 5.16:1, which clears AA for normal text with room to spare and even clears AAA for large text. Total change: about fifteen percent lightness, and honestly the page looked better afterward.

Second move: keep the hue. It's tempting to reach for a neutral grey when a warm one fails, but that quietly strips the brand out of the interface. Adjust lightness first, saturation second, hue last if at all. In this case the hue stayed within a couple of degrees and nobody in the review noticed anything except that the text got easier to read.

Third move: check the pair in reverse. Hit Swap colors and you're testing #fdfcf9 on #5e5e55, which is the inverted state used for the sidebar. Same 5.16 ratio, since the formula is symmetric, but the perceived weight differs. Light text on a dark field looks bolder, so I dropped that text one weight step to compensate. Contrast math does not capture that, and no tool will tell you.

Fourth move: fix the token, not the instance. The failing value lived in one variable used in roughly forty components. Changing the token repaired every screen at once, and I added a comment in the tokens file recording the measured ratio against each approved background, so the next person to nudge it can see what they'd be breaking.

The mistakes that let failures ship

Checking against the wrong background is the big one. Designers test grey text against pure white, get 4.6:1, and ship. In production that text sits on a card that sits on a tinted section, and the real background is #f1f5f9, which knocks the ratio to about 4.3 and fails. Always test the color the pixel actually sits on, including any translucency.

Semi-transparent overlays hide failures the same way. Text at 70 percent opacity over a background is not the color in your picker, it's a blend, and the blended value is what you have to test. Same for text on a photo with a gradient scrim: sample the lightest patch under the text, not the average.

Disabled states are the third trap, and the rules here surprise people. WCAG 2.1 exempts inactive controls from the contrast minimum, so a disabled button legally passes at 2:1. That's a loophole, not a recommendation. If users need to read a disabled label to understand why it's disabled, hold it near 3:1 anyway.

Placeholder text is the fourth. It fails constantly, usually around 2.5:1, because everyone wants it to look lighter than real input. If your placeholder carries the only hint about the expected format, it's content and it needs 4.5:1. If it's decorative, move the hint to a visible label and stop relying on it.

Where the numbers stop telling the whole story

The WCAG 2 formula is a blunt instrument and it's known to be. It systematically overrates dark text on mid tones and underrates light text on dark backgrounds, because relative luminance ignores how human vision actually handles polarity. Anyone who has shipped a dark theme has seen it: pure white on near black measures 19:1 and reads as harsh glare with visible halation around the letterforms. Softening to #e6e6e6 drops you to about 15:1 and looks better to most eyes.

Thin weights are another gap. The spec keys the threshold to size, not stroke weight, so a 24px hairline at 300 weight counts as large text and clears the bar at 3:1 while being noticeably harder to read than 16px regular at 4.5:1. When type is light, ignore the large-text exemption and target the normal-text threshold.

Non-text elements have their own rule that people forget. Focus rings, input borders, chart lines, icon-only buttons and toggle states all need 3:1 against what surrounds them. A 1px #e2e8f0 border on white sits near 1.2:1, which means the input has no perceivable boundary for a lot of users. The APCA method in the WCAG 3 drafts addresses several of these cases better, but AA is still what gets audited, so pass it first and use judgment on top.

Wiring contrast into the system

Do the checking once, at the token level, and record the result. For each text token, list which background tokens it is approved against and the measured ratio for each. That table is the thing that prevents regressions, because a color pair that was never approved is easy to spot in review.

Then automate it. A small test that walks every approved pair and asserts the minimum ratio catches the merge where someone lightens a grey by two steps to match a mockup. In Figma, mirror the same pairs as variable modes so a designer picking a surface gets only foregrounds that pass on it. Handoff gets easier too, since engineers receive token names with known-good pairings rather than hex values they have to verify themselves.

Common questions

What contrast ratio do I need for WCAG AA?
WCAG 2.1 AA needs 4.5:1 for normal body text and 3:1 for large text (at least 18.66px bold or 24px regular). UI components and graphical objects also need 3:1. AAA raises normal text to 7:1 and large text to 4.5:1.
Is this contrast checker free?
Yes. No sign-up, no email, no watermark. The whole tool runs client-side in your browser, so nothing you type is uploaded anywhere. Check as many color pairs as you want.
How is the contrast ratio calculated?
It uses the WCAG relative luminance formula. Each color is converted to linear RGB, weighted for perceived brightness, and the ratio is (lighter + 0.05) / (darker + 0.05). A ratio ranges from 1:1 (no contrast) to 21:1 (black on white).
Does contrast really affect real users?
It does, and not only for people with low vision. Thin grey-on-white text that passes at your desk fails outdoors on a phone in sunlight. Meeting AA is the floor, not the goal. When a design feels hard to read, the ratio is usually the reason.
How do I test text on a semi-transparent overlay or a photo?
Test the blended color, not the source color. Text at 70 percent opacity over a background is a mix of the two, and that mix is what the eye receives. On photos, sample the lightest patch of image that sits under the text rather than an average, because the worst-case pixel is what decides whether a word is readable.
Do disabled buttons and placeholder text need to pass WCAG AA?
WCAG 2.1 exempts inactive controls, so a disabled button technically passes at any ratio. Placeholder text is not exempt. If the placeholder carries the only hint about the expected input format, it is content and needs 4.5:1, which is why format hints belong in a visible label instead.
Why does white on black look harsh even at 19:1?
The WCAG 2 relative luminance formula ignores polarity, so it overstates how comfortable light-on-dark really is. Pure white on near black causes halation around letterforms for many readers. Softening the text to around #e6e6e6 still measures near 15:1 and reads far more comfortably in a dark theme.
What contrast do focus rings and input borders need?
Non-text UI elements need 3:1 against whatever sits next to them. A 1px #e2e8f0 border on a white card measures around 1.2:1, which leaves the field with no perceivable edge. Focus indicators, chart lines, toggle states and icon-only buttons all fall under the same rule.

Contrast is one lever in a bigger system. See how it works alongside size and spacing in the visual hierarchy principles guide, or build a whole palette with our color palette generator.