WCAG colour contrast checker

Put in a text colour and a background colour. You get the exact contrast ratio, whether it passes WCAG 2.1 / 2.2 level AA and AAA at normal and large text sizes, a live preview, and — if it fails — the closest colour that would pass.

How it works

Contrast ratio is not a perceptual judgement or a designer’s opinion. WCAG defines it as a precise arithmetic relationship between the relative luminance of two colours, and every conformance tool that follows the spec produces the same number for the same pair.

Step 1 — normalise each channel

Take the 8-bit sRGB values and divide by 255 so each channel sits between 0 and 1. #767676 becomes R = G = B = 118/255 = 0.46275.

Step 2 — linearise (undo gamma)

sRGB values are gamma-encoded, so they have to be converted to linear light before they can be averaged. For each channel c:

c_linear = (c <= 0.03928) ? c / 12.92 : ((c + 0.055) / 1.055) ^ 2.4

The low branch is a short linear segment near black; the high branch is the 2.4 power curve. Skipping this step is the single most common reason a home-grown contrast function returns wrong numbers.

Step 3 — weight the channels into relative luminance

Human vision is far more sensitive to green than to blue, so the channels are not weighted equally:

L = 0.2126 * R_linear + 0.7152 * G_linear + 0.0722 * B_linear

This is why a saturated blue on white can look strong but score badly, while a mid-green can score better than it looks.

Step 4 — form the ratio

ratio = (L_lighter + 0.05) / (L_darker + 0.05)

The + 0.05 terms model the ambient light reflecting off a real screen, which is why perfect black against perfect white tops out at exactly 21:1 rather than infinity. The formula is symmetric: swapping foreground and background never changes the ratio. That is deliberate — WCAG contrast is a property of the pair, not of which colour happens to be the text.

Rounding matters. This tool compares the full-precision ratio against the threshold and only rounds for display. A pair that computes to 4.4996 fails AA for normal text even though it displays as “4.50”. Tools that round first will wrongly tell you it passes.

What counts as “large text”

This is the detail people get wrong most often. Large text is 18pt (24px) or larger at a regular weight, or 14pt (18.66px) or larger at bold. It is not “a heading”, and it is not “16px bold”.

Minimum contrast ratios by conformance level and text size
TextDefinitionAA minimumAAA minimum
NormalAnything below 24px regular / 18.66px bold4.5:17:1
Large24px+ regular, or 18.66px+ bold3:14.5:1
UI components & graphicsBorders of inputs, icon glyphs carrying meaning, chart series3:1
IncidentalDisabled controls, decoration, text inside a photograph, logotypesExemptExempt

Meeting the 4.5:1 / 3:1 figures in that table is documented as technique G18 — ensuring sufficient contrast between text and its background.

Note that 18.66px is the px equivalent of 14pt at the CSS reference resolution of 96dpi (14 × 96 / 72 = 18.667). Browsers do not round it for you, so 18px bold is normal text and needs 4.5:1.

Worked examples

Every number below was produced by the calculator on this page. Try each pair in the tool above to see the badges change.

1. The classic boundary: #777777 vs #767676 on white

#777777 on #FFFFFF = 4.48:1 #767676 on #FFFFFF = 4.54:1

One hex digit apart, and they fall on opposite sides of the line. #777777 computes to 4.4781 and fails AA for normal text; #767676 computes to 4.5422 and passes. #767676 is in fact the darkest possible neutral grey that clears 4.5:1 on pure white, which is why it turns up in so many design systems as the “minimum safe grey”. If your muted text is #777, #888 or #999 (2.85:1), it is not accessible body text.

2. Pure black on pure white = exactly 21:1

#000000 on #FFFFFF = 21.00:1

The theoretical maximum in sRGB. It passes everything, but it is not automatically the best choice: many people with dyslexia or light sensitivity find maximum contrast causes visual fatigue and “halation”, where glyph edges appear to shimmer. A very common compromise is a near-black such as #1C2430 on white (15.62:1) — still far above AAA, noticeably calmer to read. WCAG sets a floor, not a target.

3. A saturated brand blue that looks fine and isn’t

#1DA1F2 on #FFFFFF = 2.83:1 #0B7BC0 on #FFFFFF = 4.56:1

A bright social-media blue on white scores 2.83:1 — it fails AA even for large text (3:1). The blue channel contributes only 7.22% of luminance, so a colour that reads as vivid can be luminously very close to the background. Ask the tool for the nearest passing colour and it walks the lightness down the same hue to #0B7BC0 at 4.56:1, which passes AA for normal text while still being recognisably the same blue.

4. White on an amber button — and why you should flip it

#FFFFFF on #F59E0B = 2.15:1 #000000 on #F59E0B = 9.78:1

White label on an amber call-to-action is one of the most common accessibility failures on the web: 2.15:1, nowhere near any threshold. The same button with black text is 9.78:1 and clears AAA. Use the Swap colours button to test both directions in one click — for warm mid-tone brand colours (amber, lime, cyan, hot pink) dark text is almost always the correct answer.

5. A pair that only just scrapes through

#FFFFFF on #0D6EFD = 4.50:1

Bootstrap’s primary blue with white text computes to 4.5008:1. It passes AA for normal text — by 0.0008. That is a fragile place to sit: a hover state one shade lighter, a slight opacity, or a designer nudging the blue “a touch brighter” will silently break it. When a pair lands within about 0.2 of a threshold, treat it as failing and build in headroom.

Frequently asked questions

Does it matter which colour I put in the foreground field?

Not for the ratio. The formula always divides the lighter luminance by the darker one, so the number is identical either way — that is why the Swap button never changes the result. It matters for the preview (which colour renders as text) and for the nearest-passing suggestions, which hold the background fixed and move the foreground.

My text is 18px and bold. Is that large text?

No. The bold threshold is 14pt, which is 18.66px, so 18px bold is still normal text and needs 4.5:1 for AA. This one-third of a pixel catches people out constantly. If you want a bold size that safely qualifies as large, use 19px or more — or just design to the 4.5:1 bar and stop worrying about the boundary.

Why does my bright green/yellow score so badly against white?

Because contrast ratio measures luminance, not hue difference or saturation. Green carries 71.52% of the luminance weight, so a vivid green is luminously close to white and scores poorly against it — while scoring extremely well against black. Yellow is worse still: #FFFF00 on white is 1.07:1. Colours that feel “loud” are often the least readable on a light background.

Do I have to check disabled buttons and placeholder text?

Disabled controls are explicitly exempt from WCAG 1.4.3 — inactive UI components have no contrast requirement. Placeholder text is not exempt: it is real, visible text and must meet 4.5:1. Since placeholders are usually pale grey by browser default, this is one of the most reliably failed checks in an audit. Better still, do not rely on placeholders to label a field at all; use a real <label>.

What about text on top of an image or a gradient?

WCAG requires the text to meet the ratio against whatever is actually behind it, which for an image means the worst pixel behind any glyph. There is no single hex value to type in, so this tool cannot answer it directly. Practical approach: put a solid or near-solid scrim behind the text (a semi-transparent overlay, or a text panel), sample the resulting colour with a screen colour picker, and test that. If the background is unavoidably variable, add a text shadow or outline — but note that WCAG 2.x gives no formal credit for shadows, so a scrim is safer.

Does opacity or a semi-transparent colour change the ratio?

Yes, and the alpha value itself is meaningless to the formula — what matters is the composited colour that actually reaches the eye. rgba(0,0,0,0.6) over white composites to #666666, and #666666 on white is what you should test (5.74:1). Compute the composite first, then paste the resulting hex here.

Is passing this tool the same as being accessible?

No. Contrast is one success criterion out of dozens. A page can hit AAA on every colour pair and still be unusable because of missing labels, keyboard traps, no focus indicators, poor heading structure or unlabelled images. Contrast is necessary, cheap to fix, and easy to verify automatically — which is exactly why it is a good place to start, not a place to stop.

What is APCA, and should I be using it instead?

APCA (Accessible Perceptual Contrast Algorithm) is a newer model developed for the draft WCAG 3.0. It accounts for polarity (dark-on-light behaves differently from light-on-dark), font weight and size, and generally matches perception better than the 2.x ratio — especially for dark mode, where WCAG 2.x is known to be over-permissive. However, WCAG 3.0 is a working draft with no target date, and essentially every legal and procurement requirement in force today (EN 301 549, Section 508, the European Accessibility Act, ADA settlements) references WCAG 2.0/2.1/2.2. This tool implements 2.x deliberately, because that is what you will be measured against.

How do the “nearest passing colour” suggestions work?

The background is held fixed. The foreground is converted to HSL, then its lightness is stepped up and down in 0.2% increments while hue and saturation are held constant. The first value in each direction that reaches the target ratio is reported, along with its actual computed ratio. That gives you the smallest change that gets you over the line, so the colour stays as close to your original as possible. If no colour in one direction can reach the target — for example, no lighter grey passes against white — the tool says so rather than showing an approximation.

Do you store the colours I enter?

No. Everything runs in your browser: there is no server call, and the colours you enter are never uploaded, logged or recorded anywhere. They appear in the page URL fragment purely so you can bookmark or share a specific pair, and a URL fragment is never transmitted to a server.

Use cases & limitations

Where this is genuinely useful

  • Choosing design-system tokens. Fix your background first, then find the darkest tint of your brand hue that still clears 4.5:1 on it. That token becomes your safe body-text colour for the whole system.
  • Auditing an existing palette. Walk every text-on-surface combination your components can actually produce — including hover, active, visited, error and dark-mode variants. Failures cluster in the states nobody designed on purpose.
  • Settling arguments quickly. “It looks fine to me” is not a testable claim; 3.1:1 is. Having the exact number and the threshold side by side ends the discussion in seconds.
  • Procurement and VPAT work. If you have to state conformance in writing, you need per-pair evidence, not a general impression.
  • Charts, badges and status colours. Non-text graphics that carry meaning need 3:1 against what is adjacent to them under WCAG 1.4.11 — a pale-yellow “warning” pill on white almost never makes it.

What this tool cannot tell you

  • Anything about images, gradients or video. It takes two flat colours. Composite or sample first.
  • Whether a colour pair is distinguishable to someone with colour vision deficiency. Contrast ratio is luminance-only. Red and green of identical luminance score 1:1 here — correctly — but two colours that both pass can still be confusable. Never encode meaning in hue alone.
  • Whether the text is readable. Line length, line height, letter spacing, font choice and hinting all affect legibility, and none of them appear in the formula.
  • How it behaves on real hardware. The 21:1 ceiling assumes an idealised display. A phone at 20% brightness in sunlight, an e-ink panel, or a cheap projector all deliver far less than the number suggests. Where the viewing environment is hostile, aim for AAA even if you are only obliged to hit AA.
  • Whether dark mode “feels” right. WCAG 2.x is polarity-blind. Light text on a dark background frequently measures well while looking blown out, because glyph strokes bloom. If a dark-mode pair measures 12:1 and looks harsh, trust your eyes and dial it back — you have plenty of margin.
  • Nothing here is legal advice. This is an implementation of a published formula, not a certification.

A practical workflow

  1. Set the background to your actual page surface, not to white by reflex.
  2. Paste in the text colour and read the ratio.
  3. If it fails, take the suggested darker or lighter variant rather than reaching for black — you keep the brand hue.
  4. Re-test the same pair with the colours swapped if you use the palette in both polarities.
  5. Repeat for dark mode. It is a separate palette and it needs separate evidence.

Go deeper on the conformance levels