Dark mode is not an inversion

filter: invert(1) takes about four seconds and produces a theme where your brand blue has turned orange. Here is what a real dark theme requires.

↑ Click any of these to repaint the entire site with it.

The fastest possible dark mode is one line:

html { filter: invert(1) hue-rotate(180deg); }

It takes four seconds to write and it is wrong in at least five separate ways. Working through why is a decent tour of what a dark theme actually is.

Inversion changes hue#

Subtracting each RGB channel from 255 does not just flip lightness. Blue becomes orange. Green becomes magenta. Red becomes cyan. The hue-rotate(180deg) in that one-liner is an attempt to undo the damage, and it does not, because hue-rotate operates on a different model than the inversion did.

Your brand color does not survive. If your product is recognisably blue, it is now recognisably orange, and no amount of shipping notes will stop that being the thing people mention.

Inversion is linear and perception is not#

Invert a mid-gray and you get a slightly different mid-gray. Your carefully built hierarchy of #333, #555, #777 collapses into #cccccc, #aaaaaa, #888888 — the same spacing in numbers, but the eye reads differences in the light range very differently from differences in the dark range. Tiers that were clearly separate now look nearly identical.

Chroma has to come down#

A color at full chroma on a near-black background glows. The eye adapts to a dark surround, the iris opens, and saturated light sources start to smear and halo. This is a physical effect, not a taste preference.

The same accent that reads as confident on white reads as radioactive on #0b1220. Every mature dark theme pulls chroma back — typically to 60–80% of the light-theme value — and raises lightness to compensate.

Those pairs are the same hue. The dark-theme versions are lighter and less chromatic, and that is what makes them sit correctly.

Pure black is a mistake#

#000000 backgrounds create maximum contrast with white text, which sounds good and reads badly. The halation is worst at the extremes, and on OLED displays the transition between a pure black pixel and a lit one produces visible smearing when anything scrolls.

Every well-built dark theme sits its background somewhere between 8% and 18% lightness. It also usually carries a slight hue — a blue-black, a warm near-black — because a perfectly neutral dark surface looks like a switched-off screen rather than a designed one.

Elevation inverts#

In a light theme, a raised surface casts a shadow. Shadow is the absence of light on the surface below, so it works because there is light to remove.

In a dark theme there is almost none. Shadows do very little. Elevation has to come from the surface getting lighter as it rises — which means your dark theme needs a whole set of surface tokens that your light theme does not use, and your light theme needs shadow tokens that your dark theme mostly ignores.

This is the structural reason dark mode is not a re-skin. The two themes have different mechanisms for the same job.

What a real mapping looks like#

Keep hue. Map lightness through a compressed curve rather than a straight inversion. Damp chroma. Clamp both ends so nothing reaches pure black or pure white.

function toDark(color) {
  const { l, c, h } = oklch(color);
  const L = 0.14 + (1 - l) * (0.93 - 0.14);   // invert, then compress
  const C = c < 0.03 ? c : c * 0.72;          // neutrals stay neutral
  return oklch(L, Math.min(C, maxChroma(L, h)), h);
}

Four lines, and the brand survives.

And then check it#

A generated dark theme is a starting point, not a finished one. Two things to verify by hand:

Semantic colors. Your success green and danger red both have to stay recognizable and stay distinguishable from each other. Dark backgrounds compress the usable range, and reds in particular get muddy fast.

Images and logos. Anything with a baked-in white background will now be a glowing rectangle. Anything with a dark logo will vanish. These need separate assets, and no color transformation is going to solve it for you.