Measuring contrast instead of trusting your eyes
The primary button looked fine and failed WCAG AA. Computing the ratio in the browser takes about ten lines and catches what eyeballing never will.
25 Aug 2026
The question
A vivid orange call-to-action with white text. It looked confident and perfectly legible. Was it actually accessible?
Setup
Rather than open a contrast checker and paste hex codes one pair at a time, run the formula over the whole palette at once, in the page:
function luminance(hex) {
const c = hex.replace('#', '').match(/../g).map(h => {
const v = parseInt(h, 16) / 255;
return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
});
return 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2];
}
function ratio(a, b) {
const [hi, lo] = [luminance(a), luminance(b)].sort((x, y) => y - x);
return (hi + 0.05) / (lo + 0.05);
}
Result
| Pair | Ratio | AA (4.5:1) |
|---|---|---|
| White on the accent orange | 3.12 | fails |
| White on the orange hover state | 2.58 | fails worse |
| Near-black on the same orange | 6.18 | passes |
The button that looked best was the one that failed. Bright saturated accents sit in an awkward middle band — too light for white text, too dark for black text to feel obvious.
Swapping to near-black on orange passed comfortably and, once seen, looked more deliberate than the white version. Accessibility and art direction agreed.
Learned
Contrast is the one design property you cannot assess by looking, because your eye compensates for exactly the thing the standard measures.
Ten lines of arithmetic, run once over the whole palette, replaces a whole category of "we will fix accessibility later." Do it when the palette is chosen, when nothing has been built on top of it yet.
Scroll reveals that cannot strand your content
Entry animations start at opacity 0 and wait for an observer. If the observer never fires, the page is blank forever. Testing what actually happens.
Read ProjectsThis site
A personal platform built to grow — five content collections, a design system in one file, and a publishing loop that starts on Instagram and ends here.
Read The LabThe menu that was 0.63 pixels tall
A full-screen mobile panel collapsed to nothing. No error, no warning — just a button that appeared to do nothing. The cause was a blur.
ReadGot something you want tested properly?