Contrast Checker
Check any two colors against the WCAG 2 contrast ratios for normal text, large text and UI components — entirely on this device.
Contrast ratio3.67:1
- Normal text · AA4.5:1Fail
- Large text · AA3:1Pass
- Normal text · AAA7:1Fail
- Large text · AAA4.5:1Fail
- UI components and graphics · AA3:1Pass
- The ratio is shown as 3.67, floored to 2 decimals — rounding would print 3.68 and overstate conformance.
- "Large" means at least 24px (18pt), or 18.66px (14pt) when bold — anything smaller is judged by the normal-text rows.
- No lighter version of this foreground reaches 4.5:1 against this background — change the background instead.
Normal text · 16 pxLarge text · 24 px
Both suggestions keep the hue and the chroma of your foreground and move only its lightness, until the pair reaches 4.5:1.
#2c72e54.52:1oklch(0.573 0.188 260)Lightness −0.049
No lightness on this side reaches the target against this background.
Do it offline:$ npx get-contrast '#767676' '#ffffff'
How to use it
- Type the foreground color first — hex in 3, 4, 6 or 8 digits, rgb(), hsl(), oklab(), oklch() or a CSS color name — then the background color, which has to be opaque.
- Read the ratio, then the five verdict rows beneath it: each row prints the ratio its success criterion demands and whether this pair reaches it.
- Pick the level you are held to, AA or AAA, to aim the suggestion rail at 4.5:1 or 7:1, and take the darker or lighter color it offers.
- Copy the report for a ticket or an audit note, or share the pair as a link that carries both colors in the URL fragment.
Put in a foreground color and a background color and read the WCAG 2 contrast ratio between them, together with a pass or fail for normal text, large text and non-text parts of an interface at both level AA and level AAA. When a pair falls short, the tool searches for the nearest passing color instead of leaving you to guess. The arithmetic runs in this tab.
How it works
The ratio starts from relative luminance, which is not the same thing as the lightness a color picker shows you. Each of the three sRGB channels is divided by 255 and then linearized: a channel at or below 0.03928 is divided by 12.92, and anything above it becomes ((v + 0.055) / 1.055) raised to the power 2.4. The three linear values are weighted 0.2126 for red, 0.7152 for green and 0.0722 for blue and added. Those weights show up directly: pure blue has luminance 0.0722 and pure green 0.7152, so #0000ff on white measures 8.59:1, #00ff00 on white measures 1.37:1 and #ffff00 on white measures 1.07:1.
The ratio itself is a quotient with a constant added to each side: (Llighter + 0.05) / (Ldarker + 0.05). That 0.05 stands in for the light a screen reflects even when displaying black, and it is why the scale is bounded. White against black gives (1 + 0.05) / (0 + 0.05), which is exactly 21, so 21:1 is the arithmetic ceiling and no pair of sRGB colors can beat it. Identical colors give 1:1. Because the lighter luminance always goes on top, the ratio has no direction: swapping the two fields cannot change the number, only which color the suggestions move.
Alpha is handled before the ratio, not by it. A foreground written rgba(0, 0, 0, 0.5) is not half a color; it is the color a browser actually paints when it blends yours into what sits behind it, channel by channel, as f × α + b × (1 − α). This tool performs that blend against the background you entered and measures the result, so black at 50 percent over white is measured as #808080 and scores 3.94:1 rather than the 21:1 an alpha-blind implementation reports. A see-through background is a different situation: nothing is known about what sits behind it, so the tool refuses rather than quietly assuming white.
The five rows are the three success criteria that quote a ratio. SC 1.4.3 asks for 4.5:1 for normal text and 3:1 for large text at level AA. SC 1.4.6 raises both to 7:1 and 4.5:1 at level AAA. SC 1.4.11 asks for 3:1 for user-interface components and meaningful graphics, which is why a border or an icon can be held to a lower bar than the label beside it. Every verdict is computed on the unrounded ratio and the printed number is floored, never rounded, so a pair at 4.4997:1 shows 4.49 and fails — the display can understate a pair by a hundredth, and it can never overstate one.
Examples
#767676 on #ffffffratio 4.54:1
foreground #767676
background #ffffff
normal AA 4.5:1 required — pass
large AA 3:1 required — pass
normal AAA 7:1 required — fail
large AAA 4.5:1 required — pass
non-text 3:1 required — passThe lightest grey text on white that still clears level AA for normal text: 4.542224959605253, which prints as 4.54. One step lighter and the normal-text row turns. The same pair passes large AAA, because that row also asks 4.5:1, while normal AAA at 7:1 is out of reach.
#777777 on #ffffffratio 4.47:1
foreground #777777
background #ffffff
normal AA 4.5:1 required — fail
large AA 3:1 required — pass
normal AAA 7:1 required — fail
large AAA 4.5:1 required — fail
non-text 3:1 required — passThe neighbouring grey, one byte per channel lighter, at 4.478089453577214. Two rows flip at once. It also shows the floored display doing its job: rounding this ratio to two decimals would print 4.48, which reads like a near miss rather than the clear fail it is, so the tool prints 4.47 and says why.
rgba(0,0,0,0.5) on #ffffffratio 3.94:1
foreground #808080
background #ffffff
normal AA 4.5:1 required — fail
large AA 3:1 required — pass
normal AAA 7:1 required — fail
large AAA 4.5:1 required — fail
non-text 3:1 required — passHalf-opaque black over white. The foreground row shows #808080 because that is what the blend paints, and the ratio 3.94:1 belongs to that grey: type #808080 into the foreground field and the same 3.94:1 comes back. An alpha-blind implementation reports 21:1 here, because it measures the black you wrote rather than the grey on the screen.
#3b82f6 on #ffffff, aiming at 4.5:1ratio 3.67:1
foreground #3b82f6
background #ffffff
normal AA 4.5:1 required — fail
large AA 3:1 required — pass
normal AAA 7:1 required — fail
large AAA 4.5:1 required — fail
non-text 3:1 required — pass
darker #2c72e5 · 4.52:1
lighter noneA popular mid blue on white, at 3.6779011537825332. It is fine as a border or a large heading and fails as body text. The suggestion holds the hue and the saturation and moves lightness only, landing on #2c72e5 — the smallest darkening that reaches 4.5:1. It reads 4.52:1, not 4.50:1, because the answer must be paintable: the next 8-bit step back, #2d73e6, drops to 4.46:1. There is no lighter answer, because against white every lighter blue scores worse, and the tool says so instead of leaving the slot blank.
oklch(0.7 0.32 150) on #ffffffratio 2.38:1
foreground #00c248
background #ffffff
normal AA 4.5:1 required — fail
large AA 3:1 required — fail
normal AAA 7:1 required — fail
large AAA 4.5:1 required — fail
non-text 3:1 required — failA green far outside sRGB. Relative luminance as WCAG 2 defines it is an sRGB quantity, so the color is first mapped to the closest sRGB color a standard display can paint, #00c248, and the 2.38:1 belongs to that. The chip beside the result names the substitution, so the number is never quietly attached to a color your screen cannot show.
Every example on this page is executed against the same engine that powers the tool, on every build.
Gotchas worth knowing
The number cannot go above 21 or below 1
White on black is exactly 21:1 and every other sRGB pair is lower, so a tool promising a higher figure has changed the formula. Two identical colors give 1:1, and a foreground at zero alpha composites to the background, which is also 1:1 — that is a perfectly valid reading of invisible text rather than an error.
Which notations the fields accept
Hex in 3, 4, 6 or 8 digits with or without the leading hash, the CSS color names in any casing, rgb(), rgba(), hsl(), hsla(), oklab() and oklch(). lab(), lch(), hwb(), color(), color-mix(), var() and currentColor are outside the grammar these fields register and are refused with the same message a typo gets. Each field is capped at 256 characters.
Out-of-range channels clamp, out-of-gamut colors are mapped
rgb(300 0 0) is #ff0000 in every browser, and hsl() percentages behave the same way, so those clamp silently here: the ratio still describes exactly what gets painted. An oklab() or oklch() color outside sRGB is a different matter — it is gamut-mapped and the substitution is reported, because the color you asked for and the color measured are not the same. The ratio is always measured on the eight-bit color the tool prints, so typing a printed hex back into the field returns the same number.
Large text has a size boundary, and it is not a judgement call
Large means at least 18 pt, which is 24 px, or at least 14 pt bold, which is 18.66 px. Anything smaller is judged by the normal-text rows. The tool prints that boundary with every result rather than inferring it: it cannot know what size your text will render at, so both pairs of rows are always shown.
The suggestions move lightness, and nothing else
A suggestion keeps the OKLCH hue and chroma of your foreground and bisects lightness until it just reaches the target, so the replacement still reads as the same color rather than as a different one. When a candidate leaves sRGB it is mapped back in, and if that costs saturation the tool says which chroma became which. Against a mid-tone background a target of 7:1 can be out of reach in both directions, and then both slots say so.
What a passing ratio does and does not tell you
These success criteria are about two flat colors. They say nothing about text over a photograph or a gradient, where the ratio changes pixel by pixel. Disabled controls and pure decoration are exempt from the non-text criterion.
Common errors
`blurple` is not a valid color — expected hex (3/4/6/8 digits), rgb(), hsl(), oklch(), or a CSS color name.- One message covers a misspelled color name and a notation these fields do not register, such as lab(50% 40 30). Nothing is guessed from a near miss: no ratio is printed and the verdict rows hold their places with dashes.
`rgba(255,255,255,0.5)` is a see-through background — a contrast ratio needs an opaque background to composite against. Use the color that sits behind it, or drop the alpha.- A background at half opacity has a backdrop of its own, and that backdrop is what settles the ratio. Read the flattened color out of your browser inspector and enter that. The keyword transparent is refused the same way. A see-through foreground is fine: it is blended over the background you gave and reported.
Channel `c` of `oklch(0.5 1e999 30)` is not a finite number — replace it with a real value in range.- A number too large for double precision parses to nothing usable. Treating it as zero would print a confident ratio for a grey nobody asked for, so the field refuses and names the channel. A channel written none is different: CSS computes it to 0, and that grey is what a browser paints.
Color is 258 characters — larger than the 256-character limit for a single color.- The longest color literal in any accepted notation is a few dozen characters, so anything past the cap is refused rather than trimmed into a color you did not type.
FAQ
What contrast ratio do I actually need?
For level AA, 4.5:1 for normal text and 3:1 for large text under SC 1.4.3, plus 3:1 for user-interface components and meaningful graphics under SC 1.4.11. For level AAA, SC 1.4.6 asks 7:1 for normal text and 4.5:1 for large text. Which level applies to you is a policy question this tool cannot answer; it reports all five rows so the decision is yours.
How is the contrast ratio calculated?
Each color is converted to relative luminance — channels linearized with the 0.03928 knee, then weighted 0.2126, 0.7152 and 0.0722 — and the two luminances go into (lighter + 0.05) / (darker + 0.05). For #767676 on white the luminances are 0.18116424424986022 and 1, which gives 4.542224959605253.
Why is 21:1 the highest ratio I can get?
Because of the 0.05 added to both sides of the quotient. White has luminance 1 and black has 0, so the largest quotient available is 1.05 divided by 0.05, which is 21. That constant models the light a screen reflects when it shows black, and without it the ratio against black would be unbounded.
My ratio reads 4.49 — does it pass at 4.5:1?
No, and the displayed figure is deliberately cautious about it. Verdicts are computed on the full unrounded ratio, and the two printed decimals are floored rather than rounded, so a pair that would round up to 4.50 still prints 4.49 and still fails. The tool adds a note whenever flooring and rounding disagree.
Does swapping the foreground and background change the result?
The ratio never changes, because the lighter luminance is always the numerator. What changes is which color the suggestions adjust, so swap when the color you are allowed to move is the other one. Alpha is the exception worth remembering: a translucent foreground is blended into the background, so the two fields are not interchangeable once alpha is involved.
Is this the same as the newer perceptual contrast metric?
No. This page implements the ratio defined by WCAG 2, which is the ratified requirement audits refer to today. APCA, the different metric proposed in the WCAG 3 draft, has its own checker at /apca-contrast-checker and reports numbers on its own scale that are not comparable with these.
Do the colors I type leave this page?
No. Parsing, the luminance arithmetic and the suggestion search all run in this tab. A share link puts the pair in the URL fragment, which browsers do not send to a server.
Keyboard shortcuts
- ⌘/Ctrl + Enter
- Re-check the current pair without leaving the color field.
- ⌘/Ctrl + Shift + X
- Swap foreground and background; one press of Undo puts them back.
- ⌘/Ctrl + C
- Copy the report while nothing is selected; a selection copies itself.
- ⌘/Ctrl + Shift + C
- Copy the report even when text is selected.
- ⌘/Ctrl + S
- Download the report as a .txt file, overriding the browser save dialog.
- t
- Cycle the site theme; the preview strip has its own surround control.
- ? or ⌘/Ctrl + /
- Open the shortcut sheet, where these bindings appear with your keyboard's glyphs; Esc dismisses the topmost overlay.