CONTRAST: Whether Anyone Can Read It
A colour pair either clears the WCAG 2.1 bar or it does not, and the arithmetic that decides is four lines long. This does that arithmetic, shows its working, and when a pair fails it hands you the nearest colour that passes with the hue and the saturation left alone. It will also grid a whole palette against itself, read the colours out of a screenshot, and show you the pair through the three common kinds of colour blindness, because a pass that rests on hue is not a pass.
The pair
Type a hex value, an rgb() or an hsl(), or open the picker. A CSS colour name works too.
Large text, 24 pixels and bold
Body text at fifteen pixels, the size most of a document is set in, with enough of it here to judge whether the eye has to work.
Small print at twelve and a half, where a weak pair gives itself away.
If it fails, the nearest colour that does not
Both colours keep their hue and their saturation. Only the lightness moves, and only as far as it has to.
The same pair, three other eyes
Simulated with the Machado 2009 model at full severity, in linear light.
A whole palette, every pair at once
Paste a stylesheet, a list of hex values, a JSON theme file, anything. Every colour it can find is pulled out and drawn against every other.
Drop a file of colours here, or
The colours already in a picture
Drop a screenshot, a brand sheet or a mock-up and get the eight colours it is mostly made of. The image is decoded here and is not uploaded.
Drop a PNG, JPEG or WebP here, or
What this is, and what it is not
The number is WCAG 2.1, not an opinion. Each channel is divided by 255 and put through the sRGB curve: below 0.03928 it is divided by 12.92, above it is raised to the power 2.4 after the 0.055 offset. The three are weighted 0.2126, 0.7152 and 0.0722, the Rec. 709 luma coefficients sRGB inherits. The ratio is the lighter luminance plus 0.05 over the darker plus 0.05, where the 0.05 is WCAG's allowance for light flaring off a real screen.
The ratio is truncated, not rounded. A pair measuring 4.4996 is shown as 4.49, not 4.50, so a number that fails never appears as the number it needed to reach. Most checkers round, which is why this one may read a hundredth lower than another. The pass and fail verdicts are decided on the full value, not the printed one.
The suggested colour moves lightness only. Hue and saturation are held and HSL lightness is bisected until the pair clears the bar, then nudged until the eight-bit colour clears it too. The distance moved is stated in lightness points so you can judge whether it is a change anyone will notice. The only drift is that rounding: when the answer comes out nearly white or nearly black there is so little colour left to round that the hue can land a degree or two off, which is a property of eight bits and not a choice. Where the hue cannot reach the target even at the end of the scale, it says so rather than returning something that does not pass.
Colour blindness is simulated, not experienced. The three panels use the Machado, Oliveira and Fernandes matrices from 2009 at full severity. Real dichromacy varies, anomalous trichromacy is more common than the full absence modelled here, and no matrix replaces asking somebody. Use it to catch the obvious failure, which is a pair that separates by hue and not by lightness.
Contrast is necessary, not sufficient. A pair at 21:1 can still be unreadable at eight pixels, in a thin weight, over a photograph, or in a language whose glyphs carry detail this measure does not see. WCAG 1.4.3 is a floor for flat colour on flat colour, and text over an image is outside what this page can measure.
Questions people ask
What counts as large text?
WCAG 2.1 puts the line at 18 point, which is 24 CSS pixels, or 14 point bold, which is about 18.66 CSS pixels. Below that a pair needs 4.5:1 for AA and 7:1 for AAA; at or above it, 3:1 and 4.5:1. Bold counts because a thicker stroke puts more coloured pixels on the page.
Why does my other checker say a different number?
Almost always rounding. This page truncates at two decimals so a failing value cannot print as a passing one, and some checkers round. If the gap is larger than a hundredth, check whether the other tool is applying alpha: a colour with transparency has no contrast ratio until you say what is behind it, and this page ignores the alpha channel rather than guessing.
Does a 3:1 pass mean my buttons are fine?
It means the visual boundary of the component meets 1.4.11. The label inside it is text and still needs 4.5:1, the focus indicator has its own requirement, and a control that is only distinguishable by colour fails 1.4.1 no matter what the ratio is.
Can I check a colour with transparency?
Not directly, and no honest checker can. Contrast is measured between two solid colours; a colour at 60 percent alpha has a different luminance over white than over a photograph. Composite it against its real background first and check the result.
Where do the palette colours come from when I drop a file?
Every hex value, rgb() and hsl() in the text is pulled out with a pattern match and the duplicates dropped. It does not understand your stylesheet, so a colour built from a variable at runtime will not appear, and a hex string that is not a colour at all, such as a commit hash, might.
Is the image I drop uploaded anywhere?
No. It is decoded by the browser, drawn onto a canvas no larger than 160 pixels on its long side, and counted. Nothing leaves the tab, and the page works with the network switched off.