Most dark themes fail for the same reason: someone swapped white for black and called it done. Contrast collapses, brand colours glow too hard, and shadows vanish.
Name colours by job, not by hue
Define tokens like surface, surface-raised, text, text-muted, border, accent and danger. Components only ever use those names. Each theme then maps the names to real values.
Lift surfaces with lightness, not shadow
In light themes, raised things cast shadows. In dark themes, shadows barely show, so raise elements by making their surface slightly lighter instead.
Tone down saturated brand colours
A vivid brand colour that works on white often vibrates on near-black. Keep the hue, reduce saturation a little and raise lightness until text on it passes contrast.
Test with real content
- Check body text at 4.5:1 contrast or better in both themes.
- Look at charts and illustrations, which often hard-code colours.
- View the dark theme at low screen brightness, the way people actually use it at night.