Playground

Contrast oracle

Two numbers for every pair, because they measure different things and the disagreement between them is the interesting part.

The text, icon or mark.

Any CSS colour: hex, oklch(), color(display-p3 …), or a var() you have on the page.

What it sits on.

Any CSS colour: hex, oklch(), color(display-p3 …), or a var() you have on the page.

What is being drawn

128 / 84

Blood pressure · recorded this morning

This is body text at the pair you have chosen. Read it at arm’s length, and then read it again on the worst screen you own. That is the condition the floor exists for.

Small print, a unit, a timestamp: the sizes that fail first.

APCA

not measured

Lightness contrast. Signed: negative means light text on dark.

WCAG 2.2

not measured

The ratio procurement questionnaires ask about.

Verdict

not measured

Against the published opsinjs floor for this size.

color: #1f2933;
background-color: #ffffff;

Why both numbers

The WCAG 2.2 contrast ratio is the figure written into procurement questionnaires, accessibility statements and regulation. You will be asked for it, and the answer has to be available. It is also a model of luminance contrast that predicts readability imperfectly. The imperfection shows most visibly for light text on mid-tone backgrounds, where the model is generous, and for very dark palettes, where it is harsh.

APCA is the perceptual model developed for WCAG 3. It accounts for polarity, because dark-on-light and light-on-dark are genuinely different problems, and it accounts for text size and weight. That is why its answer is a signed lightness contrast rather than a symmetric ratio. It has no legal standing.

opsinjs treats APCA as the design floor and WCAG 2.2 as the compliance floor, and publishes both for every token pair rather than choosing. A pair that fails either one does not ship.

What a passing number does not tell you

Contrast is one criterion. A screen can clear every threshold on this page and still be unreadable to the person it was built for: a number without its unit, a trend arrow whose direction is meaningless without knowing whether up is good, a status conveyed by colour alone to somebody printing in greyscale.

It also says nothing about the surface underneath. Translucent material rungs are measured against a worst-case backdrop with a minimum scrim, and that measurement is generated rather than checked by eye. See the contrast floor.

Finally: the figures published on this site describe the shipped presets. The moment you change a token they describe something else, which is what validating your theme exists for.

The same measurement, three ways

This tool posts to /api/contrast. So does pnpm contrast, which regenerates the committed conformance table, and so does the CI gate that fails a build when a measured pair regresses. There is one implementation, hand-written in lib/color/, with no colour library in the dependency tree. An answer you get here is therefore the answer the build gets, and not an approximation of it.

Contrast and APCA · Measured conformance for every token pair · Colour independence