Dark mode is a second axis, not a second stylesheet
The server cannot know which colour scheme a visitor prefers. Anything you render that depends on it will hydrate differently than it painted.

A prerendered page is written before anyone has asked for it. It has no request, no headers, and no visitor — so it cannot know whether that visitor's device prefers a dark interface.
The invariant that makes it work
There is exactly one rule, and everything else follows from it: JavaScript picks the variable name, CSS picks the value. The name depends only on page data, which the server knows. The value depends on the scheme, which only the browser knows.
Which means nothing scheme-dependent may appear in rendered markup — no hex, no class, no attribute, no inline style. Get that wrong and the page paints correctly, then flips the moment it hydrates.

Derive the colours once, at build time
A theme is a background, a text colour and an accent — plus optional dark counterparts. Everything else (hover states, borders, surfaces, gradient stops, the readable ink on top of an accent) is arithmetic, and arithmetic done during render is arithmetic done on every page view for a result that never changes.
An empty dark slot reuses its light colour, so a theme with no dark colours looks identical in both schemes — and every "does this theme have an accent?" branch stays scheme-independent.
Three states, not two
A toggle that flips between light and dark can never get back to "follow my device". The visitor's standing choice and the currently painted scheme are two different questions, and a control that offers "revert to system" has to be able to tell them apart.

Written by
Priya Raman
Design Systems Engineer
Priya works on the seam between design tokens and the components that consume them, and is unreasonably interested in how colour behaves when someone else picks it.


