Lab 9
Contrast Circuit
WCAG contrast ratio formula, explained interactively
A contrast checker tells you that a pair fails. This lab shows which way the color has to move, which channel cannot move, and why one shared color cannot pass on both a light background and a dark one.
Before you start
The number is the WCAG 2 contrast ratio. Ordinary text needs 4.5:1. Large text needs 3:1. The ratio comes from the two relative luminances, so a pair of colors is really a point on a map with text luminance across and background luminance up.
The pass region on that map is two wedges. Text can be much lighter than the background, or much darker. Between the wedges is a line where the two luminances match and the ratio is 1:1. A step that only goes downhill cannot cross that line. If the text is the lighter color and still fails, making it darker moves you toward the line, not into the other wedge.
The math, briefly
The circuit below is that formula written so each channel has a gradient. A negative gradient means raising the channel lowers the loss. A gradient of zero means the circuit cannot see that channel. One common case is a channel sitting at exactly 0: the branch that chooses the sRGB curve is on a corner, and that channel does not move until something else pushes it off zero.
The Color Lab checks every pair in a palette. This lab asks what happens when one color is asked to satisfy two of those pairs at once.
Try a pair
Start with gray on white, which fails 4.5:1. Then load saturated orange on white and read the blue row. The chart under that compares anchor weights: contrast on the vertical axis, distance moved on the horizontal, with a slider for the step. The map is the same formula for every luminance pair. The last table is one text color used on black and on a light panel.
What this shows, in plain words. Pick a text color and a background. The lab tells you whether the pair passes, which way each color would have to move to pass, and which channels cannot help. The map shows that there are two ways to pass: much lighter text, or much darker text, with a line between them that a small step cannot cross. The last table shows one text color used on two backgrounds, and why fixing it for one breaks it for the other.
What to take from this
If a brand color is the text on a light page and the fill behind light text somewhere else, it is two jobs. Moving it to fix one job can drag the other under the line. Give each background its own text color when the two jobs pull in opposite directions.
If a channel is exactly 0 or exactly 1 and its gradient is zero, do not wait for the formula to invent that channel. The fix has to come from the channels that still have a slope, or from choosing the other side of the map.
The ratios in the shared-color table are the plain formula on the shipped hex values. The circuit and that formula agree to a rounding error, except inside a ramp much narrower than one step of an 8-bit color. The tile labeled "Circuit minus reference" is that gap for the pair you have open.
Embed this lab
Host the Contrast Circuit on your own page with one element and one script. It runs entirely in the visitor's browser, sends nothing back, and adds a small credit link.
<div data-a11y-world-lab="contrast"></div>
<script src="https://a11y.world/embed.js" async></script>
The lab's styles are scoped to its own container. If your page sets global styles on buttons or inputs, they apply inside the lab too, which is usually what you want.
Related notes
-
Contrast ratio vs perceived contrast
WCAG 2 gives one number for a color pair. APCA gives a different one, and sometimes a different verdict. Where they disagree is where the pair is marginal.
-
One brand color cannot serve a light and a dark surface
A link blue used on a navy header and on a pale panel fails on one of them. Moving it to fix the header breaks the panel. The only fix that passes both is two tokens, one per surface. Here are the numbers, and why no single value works.
-
Fix a failing color with the smallest move
A color fails contrast and you want to change it as little as possible. There are two short paths, lightness alone and a mix toward black or white. They often end at the same place, and when they do not, the difference is hue. Here is how far each one travels, with numbers.
-
Charts that still read when printed in grey
A chart passes when it still carries its message on a grayscale printer. That means three lightness levels, not three hues; dash and shape before pattern; and labels on the data instead of a legend. This is the rule every chart on this site follows, written down.
-
Check the number in the browser, not in the tool
A color fixer said 11 failing pairs before and 2 after. The browser said 57 failing elements before and 10 after. Both were true. A verification pass that reads computed styles is the only number worth reporting, and here is what it found.
-
Build a palette that passes every pair
Not every pair in a palette has to pass. Every pair you use has to. Here is how to build a palette where the allowed pairs are obvious and the forbidden ones are hard to reach for.
-
Color is never the only signal
Red for error, green for success, blue for links. Take the color away and ask what is left. If the answer is nothing, the state was never communicated.
-
Terminal colors are a contrast problem too
A terminal theme is sixteen colors of small monospace text. Most themes ship with two of them unreadable, and the light variant fails in a different place than the dark one.