Home › Color contrast checker
Color contrast checker for WCAG 2.2
Exact ratio, AA and AAA pass or fail, a live preview, and a matrix that tests every pair in your palette.
Contrast checker
Excellent. Passes every WCAG level, including AAA for normal text.
Is this pair on your live site? Find every element that fails in 30 seconds. Free scan
Closest passing text color: (:1)
Palette mode: every pair at once
Paste the colors from your design tokens (up to 8). The matrix shows the ratio for every text and background combination, marked AAA, AA, Headings only, or Fail. This is the fastest way to find out which of your greys can carry body text and which are decoration only.
The single-pair checker above also gives you a link you can paste into a ticket (the URL updates as you type) and, when a pair fails AA, the closest text color that passes. Nothing is uploaded: the math runs in your browser.
Want this checked on every page, every week? AdaClear Starter is €19 a month: weekly scans of up to 150 pages on one site, an email when a new issue appears, and a dated PDF report you can hand to a client or an enterprise buyer.
How the contrast ratio is computed
WCAG does not compare colors by how far apart they look. It compares how much light each color reflects, a value called relative luminance. Every color is turned into a number between 0 (pure black) and 1 (pure white), and the ratio is the brighter number divided by the darker one, with a small constant added to both so that black on black does not divide by zero.
The steps, for a color given as sRGB channels R, G and B in the range 0 to 255:
- Divide each channel by 255 so it sits between 0 and 1.
- Linearize each channel. If the value is 0.04045 or lower, divide it by 12.92. Otherwise compute ((value + 0.055) / 1.055) raised to the power 2.4. This step undoes the gamma curve that screens apply.
- Weight the channels for how sensitive the eye is to them: L = 0.2126 R + 0.7152 G + 0.0722 B. Green dominates, which is why a mid green looks brighter than a mid blue at the same numeric value.
- Ratio = (L1 + 0.05) / (L2 + 0.05), where L1 is the lighter luminance and L2 the darker one.
function luminance(r, g, b) {
const lin = c => {
c = c / 255;
return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
};
return 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b);
}
function ratio(a, b) {
const [l1, l2] = [luminance(...a), luminance(...b)].sort((x, y) => y - x);
return (l1 + 0.05) / (l2 + 0.05);
}
The result ranges from 1:1 (identical colors) to 21:1 (black on white). The checker above rounds to two decimals, the same precision browser developer tools use. Two things follow from the formula that surprise people: swapping text and background does not change the ratio, and the ratio is not linear. Going from 4.4:1 to 4.5:1 is a tiny visual change but the difference between a failed and a passed audit.
What WCAG 2.2 actually requires
Three success criteria cover contrast. They are unchanged between WCAG 2.1 and 2.2, so anything you fix today stays fixed under the version the ADA Title II rule and the European Accessibility Act reference.
| Criterion | Level | What it covers | Minimum ratio |
|---|---|---|---|
| 1.4.3 Contrast (Minimum) | AA | Normal text | 4.5:1 |
| 1.4.3 Contrast (Minimum) | AA | Large text: at least 18pt (24px), or 14pt (about 18.7px) if bold | 3:1 |
| 1.4.6 Contrast (Enhanced) | AAA | Normal text | 7:1 |
| 1.4.6 Contrast (Enhanced) | AAA | Large text | 4.5:1 |
| 1.4.11 Non-text Contrast | AA | UI component boundaries and states, focus indicators, and the parts of icons or charts needed to understand them | 3:1 |
Three exemptions are written into 1.4.3 and worth knowing. Text that is part of a logo or brand name has no contrast requirement. Text in an inactive (disabled) control has no requirement. And purely decorative text, or text that is not visible to anyone, is exempt. Everything else, including placeholder text, link text, table captions and the labels inside buttons, must pass.
Most organizations target AA. AAA is the right target for reading-heavy products (documentation, news, education) and for any audience that skews older, because contrast sensitivity drops measurably with age. A practical middle path: hit AAA for body copy and AA everywhere else.
The failures we see on almost every site
Grey helper text on white
The single most common failure. Designers reach for #999999 or #a0a0a0 for captions, timestamps and secondary labels because it looks quiet. #999999 on white is 2.85:1, well short of 4.5:1. The lightest neutral grey that passes on a white background is #767676 (4.54:1). #777777 is 4.48:1 and fails, which is why so many audits flag it.
Placeholder text
Browser default placeholder styling is light grey and fails in every major browser. Placeholders are real text under WCAG, so either darken them with the ::placeholder selector or, better, stop relying on placeholders to label fields and use a visible label instead.
"Disabled-looking" states that are not disabled
The disabled exemption applies only to controls that are actually disabled. A muted-looking "secondary" button that still works must pass 4.5:1 for its label and 3:1 for its border. Faded rows in a table, greyed-out prices and "coming soon" tags are all live text.
Focus rings
Under 1.4.11 the focus indicator has to reach 3:1 against the colors next to it. A thin light-blue outline on a white card, or a brand-teal ring on a teal button, fails. Use a two-tone ring (a dark line inside a light one) or choose a ring color that contrasts with every background it can land on.
Text on images and gradients
Hero text over a photo passes in one corner and fails in another, and the checker can only test one pair at a time. Add a solid or semi-opaque scrim behind the text and test against the scrim color, not the photo.
Brand colors as button backgrounds
Many brand palettes were chosen for print or for a logo, not for white text. A light teal, orange or yellow button with white text usually fails. Either darken the button color (AdaClear's own CTA is #0b5e56, 7.64:1 with white) or switch to dark text on the light brand color.
Dark mode as an afterthought
Flipping a light theme by inverting colors keeps ratios only if every pair is inverted together. Mixed themes, where a light-mode grey is reused on a dark surface, are where most dark-mode failures come from. Run your dark palette through the matrix separately.
How to fix contrast in CSS without redesigning
Fix the token, not the element. Almost every failing color on a modern site comes from a handful of variables, so change them once and the whole site moves.
:root {
--text-muted: #6b7280; /* 4.83:1 on white: passes AA */
--text-faint: #767676; /* 4.54:1 on white: the floor */
}
::placeholder { color: var(--text-muted); opacity: 1; } /* Firefox lowers opacity by default */
button.secondary { color: #0b1220; background: #e2e8f0; border: 1px solid #94a3b8; }
:focus-visible { outline: 3px solid #0b1220; box-shadow: 0 0 0 5px #ffffff; }
Two more habits pay off. Keep text opacity at 1 and adjust the color instead, because opacity makes the effective color depend on whatever is underneath. And when a color must stay for brand reasons, change the text size or weight: at 24px, or 18.7px bold, the threshold drops from 4.5:1 to 3:1, which rescues many headings.
Going from one pair to the whole site
This checker answers "is this pair OK". It cannot tell you where that pair is used, how many elements fail today, or whether next week's deploy reintroduced a failing grey. The AdaClear site scan runs axe-core in a real browser against your live pages, lists every element that fails 1.4.3 and 1.4.11 with the exact foreground and background colors it measured, and then re-checks on a schedule so regressions get caught before a complaint does. Contrast is one item on the broader WCAG 2.2 checklist; the other high-volume automated finding is image alt text, which has its own free checker, and PDFs linked from your pages have the free PDF accessibility checker.
Real world example: we audited every theme in the Shopify Theme Store and measured the contrast of each add-to-cart button. 29 of 302 buy buttons fail WCAG AA, some as low as 2.05:1. Read the Shopify theme accessibility report.
Find every contrast failure on your site
Free 30-second scan of any page: score, the elements that fail, and the colors involved. No overlay, no signup.
Scan my site free →Frequently asked questions
What counts as "large text"?
At least 18 point (24 CSS pixels) at normal weight, or at least 14 point (about 18.66 CSS pixels) at bold weight (700 or heavier). Large text needs 3:1 at AA and 4.5:1 at AAA. Note that the point-to-pixel conversion assumes the default 96 dpi; if your base font size is unusual, measure the rendered size.
Does the checker handle rgb() and short hex values?
Yes. It accepts #abc, #aabbcc, aabbcc without the hash, and rgb(r, g, b) or rgba(r, g, b, a). Alpha is ignored because WCAG measures the color as rendered; if your text is semi-transparent, enter the color you see after blending, or set opacity to 1 and pick a solid color instead.
Why does my design tool report a different ratio?
Some tools round differently, some blend against a default background you did not choose, and a few use the newer APCA algorithm rather than the WCAG 2 formula. WCAG 2.2 conformance is measured with the formula on this page. APCA is a research candidate for a future WCAG 3 and is not the legal or contractual reference today.
Do icons, charts and borders really need to pass?
Under 1.4.11 the parts you need to perceive in order to understand or operate them do. An input's border, a checkbox outline, the selected state of a tab, the slices of a pie chart that carry meaning: all 3:1 against adjacent colors. Purely decorative graphics and disabled controls are exempt.
Is 4.5:1 enough for people with low vision?
It is the legal floor, not a comfort target. WCAG's own enhanced level is 7:1 for a reason: contrast sensitivity falls with age and with common conditions such as cataracts and diabetic retinopathy. If you can reach 7:1 on body copy without hurting the design, do it.
Is anything I enter sent to AdaClear?
No. The math runs in your browser and nothing is posted to a server. The page URL changes so you can share the pair; that is the only side effect. The full site scan is different: it fetches and renders the URL you give it, and the results are shown to you and not stored.