← All writing
Craft · · 8 min

I couldn't read it in the sun

The formula is a floor, everybody reads it as a target, and the thing that finally moved the gray was a photograph.

Accessibility CSS

I took my phone out to the front steps last week to look at a page I’d built, and I couldn’t read it. Light gray body text, thin weight, on white. I turned the brightness up, then I moved into the shade, then I gave up and went back in.

So I checked it properly, assuming I’d broken something. Every pair on the page cleared 4.5:1. A couple of them cleared 7. The checker had nothing to say about any of it.

Then I asked the designer to take the body gray down a couple of steps, and got back, fairly, that it passes. Which, ofc, it does. I’m also the one who put that number in the project’s acceptance criteria back in the spring, on the theory that having a number beats having a taste argument.

A floor written down as a floor arrives at the other end as a target.

Two colors in, one number out

Briefly, because the mechanism is what explains the failures.

The WCAG guidelines define contrast as a ratio between the relative luminance of two colors. Luminance comes out of the red, green and blue channels with fixed weights, roughly 21% red, 72% green, 7% blue, and the two values give you a ratio somewhere between 1:1 and 21:1. Body text needs 4.5:1. Large text needs 3:1, and since 2.1 so do interface pieces like input borders and icons.

That’s the whole thing. Two colors in, one number out.

Now go and look at what isn’t in that sentence. The size of the type, its weight, the face it’s set in, the screen it lands on, the room the screen is in, and whether the background behind it is even a flat color.

The four it can’t see

Weight is the one that got me. A ratio of 4.5:1 in a 300 face at 14px and the same 4.5:1 in a 600 face at 18px are not the same reading experience, and the guidelines have one coarse allowance for large text and nothing whatsoever for weight underneath it. Thin type at small sizes passes routinely and is punishing to read. The page on the steps was set in a light weight, and my sense is that was more of the problem than the color was.

Hue is the strangest one. The channel weights are an approximation of how the eye responds, tuned for the displays of the early nineties. They are not kind to blue. Pure blue on white lands around 8.6:1, sailing past even the strict level, and a paragraph of it is horrible to read. Nobody who has actually looked at that paragraph believes it’s nearly twice as readable as a gray sitting at 4.5:1, and the number says it is.

Then there’s the screen and the room, which is the pair I’d been quietly ignoring for years. The ratio assumes a display behaving as specified, being looked at in conditions nobody specified. A phone at 30% brightness in August sun, an aging laptop panel with a warm-shifted white point, a projector in a meeting room with the lights up. All of those compress the range, none of them are in the math, and the design was approved on two very good monitors in a room with the blinds down.

And anything that isn’t a flat color behind it. Text over a photograph has a different ratio at every pixel, and the standard advice is to check the worst part, which nobody does, and which stops being true the moment somebody swaps the image out in the CMS. If text sits on a photograph it wants a scrim or a panel under it, and that’s a design decision instead of a measurement.

In fairness to the number

The formula is also sometimes too strict, in a direction that pushes people toward worse work. Certain saturated pairs fail while being perfectly legible, oranges and yellow-greens especially, and the standard response is to darken the brand color until it goes muddy. That’s a real cost paid to a number, and I’ve watched it get paid.

More to the point though, when my eye and the number disagree and the number says fail, the number wins. Every time. Mine is one pair of eyes, in one room, belonging to somebody whose vision is currently fine, and “looks alright to me” is the exact reasoning the standard exists to overrule. Every blind spot above runs in the direction of the number being too generous. I’ve yet to find a case where ignoring a failure turned out to be the right call.

The lightest gray that passes

So it’s a floor. 4.5:1 is the least anybody could take you to task over instead of a goal, and on anything a person has to read for more than a few seconds I go for 7:1, which costs almost nothing on most palettes and buys back all four of those blind spots at once.

I’ve also stopped putting thin weights anywhere near the bottom of the range. If the type is 300 I want a lot of headroom or I want a heavier face, and on body copy I’ve more or less stopped going under 400.

The other thing worth memorizing is where the edge sits, because designers reach for grays around #999 constantly, it looks fine on a good monitor, and it isn’t close.

/* 2.85:1 on white, fails */
color: #999;

/* 4.54:1 on white, the lightest gray that passes for body text */
color: #767676;
CSS

Then check the muted text in both themes, since a gray that sits comfortably as a secondary on white is usually too dim on near-black. I wrote that down last year and then went and got it wrong again anyway, which is about the going rate for things I write down.

Go outside

None of which found the actual problem. I found it on the front steps with a phone, not looking for anything.

So that’s a step now. Once a page is more or less put together I look at it outdoors, or at a window if I can’t get outdoors, at whatever brightness the phone happens to be sitting on. It takes 30 seconds, it needs no tooling, and it’s the only check I run with the room in it. It’s the same move as going through a page on the keyboard, which I started doing to my own work last month and which turned up something no tool had ever mentioned to me. Both of them find more than the automated pass does, which I’d have called an unfair comparison in the spring and now think is just true.

It’s also what settled the gray. I couldn’t argue the designer out of a number I had personally written into the acceptance criteria. So I stopped trying, took a photo of the phone in the sun with the page on it, and put the photo in the project channel. The gray was two steps darker by that afternoon. Nobody mentioned the ratio at all, in either direction. I think that was the most honest part of the whole exchange.

They’re working on a better one

Worth knowing about, if only so nobody treats the current numbers as permanent. There’s active work on a successor that accounts for size and weight, uses a perceptual model instead of the old luminance math, and treats light-on-dark differently from dark-on-light, which the current one doesn’t do at all. It gives quite different answers in places, particularly for light text on dark backgrounds and for exactly the thin-type case that started this.

It’s a draft and it’ll be years before anybody has to comply with it, so I’m not designing against it yet. But it’s the clearest confirmation I’ve found that the gap between passing and readable is a known gap instead of me being precious about my phone.

Keep testing contrast in the meantime. I’d be far more worried about somebody deciding the whole exercise is theater than about somebody trusting the number too much. It catches a great deal, it takes seconds, and it’s one of the very few accessibility checks a machine can genuinely do, which is more than can be said for the alt attributes it also reports zero errors on. It belongs in every pass, next to the other quick ones.

I’ve stopped treating a green column as the end of the conversation. It’s a lower bound on one variable, computed for a screen I’m not looking at, in a room nobody is standing in, and the page it approved was one I couldn’t read sitting on my own front steps. I still run it on everything. I’ve just stopped handing anybody a screenshot of it as though that were the answer.

Read similar posts
8 min

color-scheme and the white flash

A client's site was flashing white on every load for anyone whose operating system is dark, and the declaration that fixed it also closed the unreadable checkout dropdown I'd had filed under Chrome being weird for months.

2 min

prefers-reduced-motion

An operating system setting that has been sitting there since 2018 telling you somebody gets motion sick, and the two-line stylesheet that is the whole of respecting it.