Design systems
Dark mode without a second design system
A dark theme is not a colour inversion. It is a second mapping of the same tokens. Here is how to avoid two products and two sets of components.
Key points
- Dark mode is a token mapping, not a duplicated palette.
- Contrast, images and charts are the first three things to break.
- If you need two sets of components for two themes, the problem is in the tokens.
Dark mode looks like a colour change. It is really a test of the design system. If your components know the semantic roles, the theme switches by itself. If they know raw colours, you have to duplicate everything.
Tokens, not colours
A component should not know that text is #f5f5f5. It should know that text is --ui-fg. The dark theme changes the token value, not the component. A card with a hard-coded background: #111 stays black in light mode.
| Role | Dark | Light |
|---|---|---|
| Canvas | Near black | Warm white |
| Text | White | Near black |
| Border | Dark grey | Light grey |
| Accent | Brighter blue | Deeper blue |
Contrast: the test that fails first
Secondary text is the first thing to vanish. Check 4.5:1 for body copy, 3:1 for large text and for the focus ring. Do not reuse the same grey for text and borders: they are different roles.
Images and charts
A bright image on a dark background can glare. Give it a border or a surface behind it. In charts, do not invert the palette: change the lightness of the series and keep the contrast between them.
The three-screen test
- Open the same page in both themes and compare the secondary text.
- Tab through with the keyboard: is the focus ring visible in both?
- Open a dialog and a toast: are the surfaces distinguishable?
- Look at an image that has both light and dark areas.
- Check the system theme, not just the toggle.
The full token specification lives in EneUI. Start from a semantic layer, not from a second palette.
Frequently asked questions
Is dark mode just inverting the colours?
No. It is a mapping of semantic roles: canvas, surface, text, border, accent. Inversion produces the wrong contrast.
Do I have to duplicate the components?
No. When components use semantic tokens, the theme changes with no duplication at all.
How do I pick the background black?
A near-black reads better than #000 and leaves room for hierarchy. Avoid pure black on large surfaces.
Do images need special handling?
Yes. Give images that glare a border, a surface behind them, or a version with different luminance.
How do I test the system theme?
Change the operating system setting, not just the site toggle, and check that the preference is read on first paint.