How a contrast ratio is actually calculated
Most colour decisions are taste. Contrast ratio is not. It is a fixed calculation with one right answer for any two colours. It is short enough to do by hand once, and doing it buys you the thing that surprises people most: two colours that look about equally bright can produce wildly different ratios, and the reason is visible in the arithmetic.
The byte in a hex code is not an amount of light
Start with what a colour code contains. #4ade80 is three bytes: red 74, green 222, blue 128, each on a scale of 0 to 255. The obvious move is to treat those as amounts of light and average them. That is wrong twice over, and both errors matter.
The first is encoding. sRGB, the colour space your screen and your CSS use by default, stores channel values on a gamma curve. The stored number is not proportional to the light emitted — doubling the byte does not double the photons. So the first step is to linearise each channel.
Divide the byte by 255 to get a value c between 0 and 1, then:
- If
cis at or below0.03928, the linear value isc / 12.92. - Otherwise it is
((c + 0.055) / 1.055)raised to the power2.4.
The straight-line piece at the bottom is there because a pure power curve has an unhelpful slope near zero; the exponent does the work everywhere else.
The three channels do not count equally
The second error is treating red, green and blue as interchangeable. They are not, because the eye is not equally sensitive to them. Relative luminance is a weighted sum of the linearised channels:
L = 0.2126 × R + 0.7152 × G + 0.0722 × B
Those three weights add to 1, and green carries almost ten times the weight of blue. That single fact explains most of the confusion around contrast. Take the two extremes. Pure green #00ff00 linearises to 1 in the green channel and 0 elsewhere, so its luminance is exactly 0.7152. Pure blue #0000ff lands at exactly 0.0722. Against a white background the green gives a ratio of 1.37 and the blue gives 8.59 — the same channel pushed to the same maximum, six times apart in the result.
The ratio itself
Once both colours have a luminance, the ratio is:
(lighter + 0.05) / (darker + 0.05)
The 0.05 added to both is an allowance for ambient light reflecting off the screen, and it also pins the scale at both ends. Black has luminance 0 and white has 1, so the largest ratio possible is (1 + 0.05) / (0 + 0.05) = 21. Two identical colours give 1. Every real pair sits somewhere between 1:1 and 21:1, and nothing can go outside that.
One example, end to end
Take the tool's own defaults: text #111827 on a background of #4ade80.
- Split the bytes.
#111827is17, 24, 39.#4ade80is74, 222, 128. - Scale to 0-1.
17 / 255 = 0.0666667,24 / 255 = 0.0941176,39 / 255 = 0.1529412. - Linearise each. All three are above
0.03928, so they take the exponent:0.0056054,0.0091341,0.0202886. - Weight and add.
0.2126 × 0.0056054 = 0.0011917,0.7152 × 0.0091341 = 0.0065327,0.0722 × 0.0202886 = 0.0014648. The luminance of the dark colour is0.0091892. - Do the same for the green. The scaled channels are
0.2901961,0.8705882,0.5019608; linearised they are0.0684782,0.7304607,0.2158605; weighted they are0.0145585,0.5224255,0.0155851, for a luminance of0.5525691. - Divide.
(0.5525691 + 0.05) / (0.0091892 + 0.05)is0.6025691 / 0.0591892 ≈ 10.1804, which the tool prints as10.18.
Look at step 4 again. The blue byte, 39, is the largest of the three, and it contributes 0.0014648 — less than the green byte of 24 contributes at 0.0065327. A channel with more than one and a half times the raw value ends up carrying under a quarter of the weight. That is why a plain average of R, G and B is not luminance.
What the tool reports, and what it cannot
The free Contrast Checker runs exactly the steps above on two six-digit hex colours and prints the ratio to two decimal places. Alongside it, it shows whether that number reaches 4.5 and 7.0, the two threshold ratios named for normal-size text in WCAG 2.1. That is a statement about where one number falls relative to two others. It is not a verdict on your page.
Two limits are worth stating plainly. First, the ratio is computed from opaque sRGB colours only — no alpha, no overlaying image, no gradient behind the text. If your background is a photograph, the number applies to the flat colour you fed it and to nothing else. Second, and more important: a high ratio is a necessary condition for readable text, not a sufficient one. It says nothing about type size, weight, line length, letter spacing, or the reader. Thin nine-pixel type at 12:1 can be harder to read than comfortable type at 5:1. Get the number right, then go and look at the thing.