how i did it

The five things every page on my sites ships now

I run four live sites. For a while, each one had its own idea of what a “finished” page looked like. One had a dark mode that fought the browser’s own scrollbar colors. Another had zero support for people who turn off motion in their OS settings. A third animated everything with transition: all, which is a fast way to animate a color you never meant to move.

An audit across all four found the same five gaps, repeated. So I stopped fixing them one at a time and wrote a checklist instead. Now every page on every site ships all five, and a script proves it.

1. color-scheme set to match the real theme. Without it, native browser chrome (scrollbars, form controls) renders with the wrong contrast on a dark page. One CSS declaration, and it fixes something the rest of your dark mode work cannot touch.

2. A theme-color meta tag on every page. This one cannot live in a shared stylesheet. It has to sit in the head of every individual page, which is exactly why a second layout template ships without it and nobody notices for weeks.

3. A prefers-reduced-motion: reduce block. One media query that kills animation and transition duration for anyone who asked their OS not to move things around. Cheap. Almost always skipped.

4. touch-action: manipulation and a tuned tap highlight color on every control. Mobile Safari and Chrome both add a 300ms delay and an ugly gray flash to taps unless you tell them not to.

5. A visible :focus-visible ring, and never transition: all. Name the properties you are transitioning. all is how you end up animating something you never intended to move, and it is invisible until it is not.

Here is the part that actually mattered: the check has to run against the shipped output, not the source. I wrote a source-level grep first and it reported all four sites clean of transition: all. They were not. The offending line only existed in the built dist/ folder, generated by a bundler step the source grep never saw. A check that reads the wrong tree tells you a comforting lie.

The fix was a script that scans the built site, not the repo: check-baseline.mjs. It runs in CI on one site, as a postbuild step on another, and inside the documented deploy recipe on the two that do not have CI wired up yet. Same five checks, same script, copied into each project rather than reinvented.

None of these five things are hard. The reason they were all missing at once was not difficulty. It was that nothing was checking for them, so nothing forced the question. A checklist that only lives in your head gets skipped the first time you are moving fast. A checklist that lives in a script that fails your build does not.

If you are building more than one site and comparing them by eye, you already know how this goes: the first one gets the fix, the other three quietly do not. Write the check once, point it at what actually ships, and stop trusting yourself to remember five things across four repos.

> I send a short AI report every week. What shipped, what matters if you build with AI.

Subscribe free →