Accessibility Contrast Checker
Check text and background colors against WCAG AA and AAA thresholds, with font-size guidance and a suggested passing color.
🔒 This tool runs entirely in your browser. Your files are never uploaded to a server.
Contrast ratio
—
How it looks
Sample heading over this background
This paragraph shows the text colour on the background colour at the size and weight you chose. Read it, then check the verdicts above — the numbers and your eyes should agree.
Boundary examples
How little it takes to cross a threshold. Click one to load it.
How to use it
- Enter your text and background colours, as hex or with the pickers.
- Set the real font size and weight — that decides which threshold applies.
- Read the verdicts; the one that applies to your case is marked.
- If it fails, take the suggested nearest passing colour.
The formula, and why it stops at 21
linearise each channel, then
L = 0.2126·R + 0.7152·G + 0.0722·B
ratio = (L_lighter + 0.05) / (L_darker + 0.05)
black on white → (1.00 + 0.05) / (0.00 + 0.05) = 21.00
The green coefficient is more than three times the red one and roughly ten times the blue, because that is roughly how much each contributes to perceived brightness. It is why a saturated blue on black is far harder to read than its numeric lightness suggests, and why yellow on white is worse than it looks. The +0.05 on both sides is a flare term standing in for ambient light reflecting off a screen; without it the ratio would run to infinity and black on white would be unbounded rather than 21:1.
One hex step decides compliance
#767676 on white 4.542:1 passes AA
#777777 on white 4.478:1 fails AA
#595959 on white 7.005:1 passes AAA, barely
Nobody can see the difference between those first two greys, and only one of them is compliant. That is the whole argument for measuring rather than judging — and it is also why "it looks fine to me" is not a defence in an audit. The three examples are loadable from the buttons at the bottom of the tool so you can watch the verdict flip.
The large-text boundary catches people out
Large text is allowed 3:1 instead of 4.5:1, and the definition is a hard line at 24 px, or 18.66 px if the text is bold. A subheading at 22 px regular gets no concession at all. Because designers think in a type scale and the guidelines think in pixels, this is a common source of "but the heading passes" — it does not, unless it reaches the threshold. The tool states which side of the line your size and weight fall on rather than leaving you to remember it.
What a passing ratio does not prove
Contrast is one criterion among many, and it is easy to treat a green tick as a finished job. It says nothing about whether the type is big enough, whether the line length is readable, whether text over a photograph keeps its contrast across the whole image, or whether colour is the only cue marking something as a link. It also has a known blind spot: on dark backgrounds the WCAG 2.x model rates some pairs as comfortably passing that readers find genuinely difficult, which is one of the reasons a replacement model is under development for the next version of the guidelines.
Until that is ratified, this formula is what conformance claims, procurement questionnaires and legal requirements refer to — so it is what this reports, without pretending it is the last word.
FAQ
What ratio do I actually need?
4.5:1 for normal body text at AA, 3:1 for large text, and 3:1 for icons, borders and other non-text elements you need to see. AAA raises body text to 7:1. Most legal and procurement requirements point at AA, so 4.5:1 is the number to design to.
What counts as large text?
24 px at any weight, or 18.66 px when bold — which is 18pt and 14pt bold respectively. That single boundary decides a lot of failures, because a heading at 22px regular is still held to 4.5:1 while the same colour at 24px only needs 3:1. The tool tells you which side of it you are on.
How tight are these thresholds really?
Tighter than people expect. #767676 on white measures 4.542:1 and passes AA; #777777 on white measures 4.478:1 and fails. Those two greys are visually indistinguishable, and one is compliant while the other is not — which is exactly why guessing does not work and a number is needed.
Does contrast alone make text accessible?
No. It is a necessary condition, not a sufficient one. Contrast says nothing about font size, line length, spacing, whether the text sits over a photograph that varies, or whether colour is the only thing distinguishing a link from body text. A page can pass every ratio here and still be hard to read.
Is this formula the final word?
It is the current standard and it has known weaknesses, particularly on dark backgrounds where it can rate pairs as passing that are genuinely hard to read. A different model called APCA is being developed for the next version of the guidelines and sometimes disagrees. Until it is ratified, WCAG 2.x is what audits and procurement use, so it is what this reports.
Why does the suggested colour keep my hue?
Because jumping to pure black always passes and always looks wrong. The fix walks your colour towards black or white in small steps and stops at the first one that meets the target, so you get the nearest compliant version of the colour you chose rather than a different colour entirely.
How we compare
| Feature | Online Tool Store | Other contrast checkers | Browser devtools |
|---|---|---|---|
| Marks which requirement applies to your size | ✓ | Lists all, unmarked | ✓ |
| Suggests the nearest passing colour, hue kept | ✓ | Some do | Suggests a colour |
| Live preview at your real size and weight | ✓ | Fixed sample | The real page |
| Explains the model's known weaknesses | ✓ | ✗ | ✗ |
| Works before anything is built | ✓ | ✓ | ✗ |
| Checks a whole live page automatically | ✗ | ✗ | ✓ |
| Handles text over gradients and images | ✗ | ✗ | Partly |
Best used while choosing colours, before anything is built — it tells you which threshold applies and hands you the nearest compliant version of the colour you wanted. For auditing a page that already exists, your browser's devtools will check every element at once, which this cannot.