Our first dark mode technically worked. Flip the system switch, and the background went dark, the brand teal adapted, and nothing crashed. It also looked wrong in a way that's hard to describe until you've seen it: cards stayed a washed-out white, body text stayed close to black, and a glass effect that was supposed to look frosted painted pale smears across a dark background instead. It was dark mode in the sense that the lights were off. It wasn't dark mode in the sense that anyone had actually designed for the dark.

The reason is almost boring. Most of our color tokens were hardcoded to light-mode hex values. When the system said "dark," only a couple of them knew what to do with that. Everything else just kept rendering its light-mode self on a black canvas, which is the visual equivalent of wearing sunglasses indoors and calling it an outfit change.

Designing dark first, then encoding it

So we stopped patching the symptom and did the part we'd skipped: we actually designed three dark treatments (Forest Night, Graphite, and one we ended up calling Midnight OLED) as real mockups, side by side with the approved light designs, before writing a line of code. Midnight OLED won. It's a near-black ground, the same aurora background effect from the light theme but dimmed down until it reads as embers instead of a gradient, frosted glass cards at a faint 6% white opacity, warm off-white text, and a bright mint accent color reserved for the moments that need to pop.

Once that was settled, encoding it was mechanical: every color in the design system now carries a light value and a dark value as a pair, in one file, so all twenty-five or so screens that consume those tokens picked up the correct dark styling automatically instead of needing individual fixes. The background effect and the glass-card fallback both became scheme-aware the same way. It's the kind of change that looks small in a diff and would have been a much bigger mess if we'd tried to bolt it on screen by screen instead of fixing it at the token level.

Then we checked our own work

Design reviews are easy to fake if you only look at the screens you already know are fine. So we rendered the approved reference mockups, captured all seventeen app screens in both light and dark automatically, and diffed every one of them against its reference, element by element. Two rounds of fixes came out of that: header spacing on the home screen, a paywall redesign around a single clear call to action, a cleaned-up rewards screen, among others.

Some gaps didn't get fixed, and we're not hiding that list either. The calendar's day view is still a simple list where the approved design shows an hourly timeline. Task rows are missing the points chips and progress indicators from the mockups, because that needs data model work we haven't done yet. Settings is missing its profile card. We wrote all of it down as a known, remaining list rather than letting "pixel perfect" quietly mean "perfect except for the parts we didn't check."

← Back to all posts