How to Design Dark Mode for an App or Website
Practice4 minInnovialabs editorial team
In short
A dark theme is not inverted colors: use dark grey instead of pure black, show depth with lighter surfaces, switch accents to lighter and less saturated tints, check contrast in both themes and build everything on semantic tokens with light and dark values.
The short answer
A good dark theme is designed, not generated. Five rules cover most of the work:
- Dark grey background, not pure black.
- Depth through lighter surfaces, not shadows.
- Lighter, less saturated accent colors.
- Contrast checked in both themes.
- Semantic tokens so components switch themes automatically.
Avoid pure black and pure white
White text on #000000 creates very sharp contrast. For some readers letters start to blur or glow, and long reading gets tiring. Pure black also leaves no room for depth: you can't go darker for the background, and shadows disappear.
Start with a dark grey base — Material Design, for example, uses #121212 as its baseline dark surface. For text use slightly dimmed white instead of #FFFFFF, and a softer grey for secondary text.
Pure black still has a place: some apps offer a separate "true black" option for OLED screens. Just don't make it the default without testing.
Elevation with lighter surfaces
In a light theme, depth comes from shadows. On a dark background shadows are barely visible, so the logic changes: the higher the surface, the lighter it is.
| Level | Example elements | Surface |
|---|---|---|
| 0 | Page background | Darkest |
| 1 | Cards, list items | Slightly lighter |
| 2 | Headers, sticky bars | Lighter still |
| 3 | Menus, dialogs, popovers | Lightest |
Keep the steps small and consistent. Thin borders in a subtle light tone also help separate surfaces where lightness alone isn't enough.
Desaturated accents
Bright, saturated colors that look fine on white tend to "vibrate" on a dark background and often fail contrast. Use lighter and less saturated tints of the same hue: if a palette has shades from 50 to 900, a dark theme typically uses lighter steps than the light theme does.
The same applies to status colors: error red, success green and warning yellow all need dark-theme versions. Check that the brand still reads as the brand — the hue stays, only lightness and saturation shift.
Check contrast in both themes
WCAG thresholds are the same for both themes:
- 4.5:1 for normal text;
- 3:1 for large text;
- 3:1 for UI component boundaries, icons and meaningful graphics.
Commonly missed in dark mode: secondary and placeholder text, input borders, focus rings, dividers, chart colors and disabled states. Also review images: logos with dark elements on a transparent background disappear, so prepare light variants.
Set up light and dark tokens
Build two layers:
- Primitive tokens — the raw palette:
violet-300,grey-900. - Semantic tokens — roles:
bg,surface,text,text-muted,border,accent. Each has a value for light and for dark.
Components use only semantic tokens. Then switching themes means swapping values, not redrawing screens. In Figma this maps to Variables with Light and Dark modes; in code — to CSS custom properties:
:root {
color-scheme: light dark;
--bg: #ffffff;
--surface: #f4f3f7;
--text: #1a1033;
--text-muted: #5c5670;
--accent: #7c3aed;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #121212;
--surface: #1e1e22;
--text: #ececf1;
--text-muted: #a3a1ad;
--accent: #a78bfa;
}
}
color-scheme tells the browser to adapt native controls and scrollbars too. For a manual switch, add the same overrides under an attribute such as [data-theme="dark"].
Switching behavior
- Default to the system setting.
- Offer a manual override (Light / Dark / System) and remember the choice.
- On the web, apply the theme before the first render so the page doesn't flash the wrong colors on load.
Common mistakes
- Automatically inverting colors instead of designing the theme.
- Pure black backgrounds with pure white text.
- The same saturated brand colors as in the light theme.
- Relying on shadows for depth.
- Hardcoded hex values inside components that ignore the theme.
- Forgotten logos, illustrations and charts.