All articles
Frontend

Dark mode and theming done right

6 min read
Dark mode and theming done right
Short version: dark mode done properly is a second designed palette, not a filter. Define every colour as a token with a role — surface, text, border, accent — and swap the values, never the markup. Avoid pure black and pure white, re-check contrast in both themes, respect the system preference by default while offering a toggle, and set the theme before first paint so nobody sees a white flash.

Dark mode is a genuine accessibility and comfort feature for a large number of people, and it's also the single most common place where a good design system quietly falls apart. The reason is almost always the same: the second theme was added at the end, by inverting things, rather than designed alongside the first.

Start with tokens, not colours

The foundation of any theming system is naming colours by role rather than appearance. A variable called --surface-raised or --text-secondary can hold a different value in each theme and every component keeps working. A variable called --light-grey becomes a lie the moment the background turns dark.

Get the roles right and theming becomes a data change: one block of variables for light, one for dark, and no component ever knows which is active. Get them wrong and you'll be writing per-component overrides forever.

Design the dark palette properly

The switching mechanics

Three behaviours make a theme system feel considered. First, default to the system preference — the user already told their device what they want. Second, offer an explicit toggle with three states if you can: light, dark, and follow-system. Third, persist the choice so it survives a reload.

Then handle the one bug everyone ships at least once: the flash of wrong theme. If the saved preference is applied by your main JavaScript bundle, the browser has already painted a white page by the time it runs. The fix is a very small inline script in the document head that reads the stored preference and stamps it on the root element before anything renders.

If your dark mode was generated rather than designed, users can tell within a second.

Beyond dark mode

A token system that supports two themes usually supports more for free — high-contrast modes, per-customer branding in a white-label product, seasonal variants. That's the real return on the investment: you didn't build dark mode, you built theming, and dark mode was the first customer. Respect reduced-motion and reduced-transparency preferences with the same mechanism and you've covered most of what users ask for.

Key takeaways
  • Name colour tokens by role, so themes are a data swap rather than a rewrite.
  • Design the dark palette deliberately — no pure black, no pure white, desaturated accents.
  • Show elevation with lighter surfaces, not shadows.
  • Default to the system preference, offer a toggle, and remember the choice.
  • Apply the theme before first paint to avoid the white flash.

Frequently asked questions

Is dark mode just inverting the colours?

No. Inverting produces harsh, unreadable interfaces — pure white text on pure black causes halation, and inverted brand colours often break entirely. Dark mode is a second designed palette where every colour is chosen for a dark surface, not calculated from the light one.

Should the site follow the system setting or a toggle?

Both. Default to the operating system preference so the site matches what the user already chose, and offer a toggle for people who want something different on your site specifically. Remember their choice so it persists between visits.

What is the flash of wrong theme and how do I stop it?

It's the moment a page paints in light mode before the script applies the saved dark preference. Fix it by setting the theme on the root element in a tiny inline script that runs before the page renders, rather than after your main JavaScript loads.

ZIVARA builds design systems that scale across themes, brands and platforms. Let's talk. Related: design systems that scale and building accessible UI components.

Have a project in mind?

ZIVARA builds custom web, mobile, cloud and AI software — and our own products. Let's talk about what you want to ship.

Get in Touch