Why the filter fails
The naive dark mode inverts the colours and calls it done. It fails in the same four places every time: shadows (a dark surface cannot cast a convincing dark shadow — elevation must be expressed as *lighter* surfaces, not deeper ones), saturation (brand colours that glow on light become neon on dark), text contrast (the 'muted grey' that passed on white dies on near-black), and imagery (photos shot for light backgrounds arrive as glowing rectangles). Dark mode is not the same design with a colour swap; it is a different physical metaphor — light on dark is a screen emitting light, and the system must be designed for that physics.
Light mode: surface rises = DARKER + shadow
--surface-1: #fff --surface-2: #f6f6f4
shadow: 0 2px 8px rgb(0 0 0 / .10)
Dark mode: surface rises = LIGHTER + border
--surface-1: #121318 --surface-2: #1a1c23
border: 1px solid rgb(255 255 255 / .08)
(shadow becomes almost useless; the border
does the separating)The token architecture that survives
- Role tokens, not colour tokens: --surface-1, --text-primary, --text-dim, --border — components reference roles, and the theme provides the values; a component that references --ink directly has already broken in one of the two themes.
- Dark is a luminance problem first: text hierarchy in dark mode comes from luminance steps, not opacity alone — 'dim' text needs to be a lighter grey with a *defined* ratio, because opacity-on-dark compounds differently than opacity-on-light.
- Saturation discipline: the dark palette desaturates brand colours 15–25% — the same hue at full saturation on near-black vibrates; the dark theme's brand is the brand, tuned for its medium.
- Semantic tokens carry the system: --danger, --success, --focus-ring are the same roles in both themes with different values — the component never knows which theme it is in, which is exactly the point.
The second-system checklist
A dark mode done as a system ships with its own audit: contrast pairs recomputed (not inherited — a pair that passed on light can fail on dark), imagery strategy decided (dark-mode imagery, or a scrim treatment for light photos), reduced-motion respected in both themes, and the switch itself saved and system-aware (prefers-color-scheme as the default, manual override on top). The tell of a real system: a designer can theme a new component in both modes from the token sheet alone, without asking what the dark 'version' of a colour should be — because there is no dark version. There is one component and two themes.