How to Build CSS Dark Mode (2026)
Dark mode is no longer optional. Users expect it, and operating systems default to it. A well-implemented dark theme reduces eye strain in low light, saves battery on OLED screens, and gives your design a premium aesthetic. The key is using CSS custom properties (variables) to create a token system that makes theming systematic rather than ad hoc.
- Implement dark mode with CSS custom properties and prefers-color-scheme.
- Covers building a color token system.
- Covers detecting user preference automatically.
- Covers adding a manual toggle.
- Covers dark mode color best practices.
Building a Color Token System
Instead of hardcoding colors throughout your CSS, define semantic tokens on :root. Use names that describe purpose, not appearance: --color-bg, --color-surface, --color-text-primary, --color-text-secondary, --color-accent. This makes theming a matter of changing variable values rather than finding and replacing hex codes.
Define your light theme tokens on :root as the default. Then override them for dark mode. Every color in your CSS references a token, so changing the token values instantly retheming the entire site.
Keep your token count manageable. Most sites need 8-12 color tokens: background, surface (cards/containers), border, text-primary, text-secondary, text-tertiary, accent, accent-hover, success, warning, error.
Detecting User Preference Automatically
prefers-color-scheme is a CSS media feature that detects the user's OS setting. When someone has their phone or computer set to dark mode, prefers-color-scheme: dark matches and your dark theme applies automatically.
-webkit-backdrop-filter alongside backdrop-filter for Safari support. Without the prefix, the effect is invisible to roughly 25% of mobile users.Implementation: wrap your dark token overrides in @media (prefers-color-scheme: dark) { :root { --color-bg: #121212; ... } }. Users get the right theme instantly with zero JavaScript and no flash of wrong theme.
This approach respects user autonomy. People who prefer light mode get light. People who prefer dark get dark. No one has to search for a toggle or dismiss a prompt.
Adding a Manual Toggle
Some users want to override their OS setting for specific sites. A toggle button lets them switch themes manually. Use JavaScript to add a data-theme="dark" attribute to the html element.
backdrop-filter inside a position: fixed element can cause severe scroll performance issues. Test thoroughly on real iOS devices.In CSS, duplicate your prefers-color-scheme rules inside [data-theme="dark"] { :root { ... } }. The attribute-based selector takes priority, overriding the media query when the user makes an explicit choice.
Store the user's preference in localStorage so it persists across page loads. On page load, check localStorage before rendering to prevent flash of unstyled content (FOUC). Apply the stored theme during the head element parsing, before the body renders.
Dark Mode Color Best Practices
Never use pure black (#000000) as your background. Dark gray (#121212 to #1a1a2e) is easier on the eyes and gives surfaces room to elevate. Material Design recommends #121212 as the baseline dark surface.
Reduce text brightness to approximately 87% opacity white for primary text and 60% for secondary text. Pure white (#ffffff) text on dark backgrounds creates excessive contrast that causes eye strain during extended reading.
Desaturate your accent colors for dark mode. A vibrant blue (#2563EB) that looks great on white can feel harsh on dark backgrounds. Shift it lighter and slightly desaturated (#60A5FA) for dark contexts. The Dark Mode Generator handles these adjustments automatically.