In 2019, Apple added system-wide dark mode to iOS 13 and macOS Mojave. Within months, dark mode adoption went from a niche developer preference to a mainstream user expectation. By 2024, over 82% of smartphone users use dark mode either always or some of the time (Android Authority survey).
Your website visitors are looking at dark interfaces everywhere. When they arrive at your site and it blinds them with a white background at 11pm, you've already lost ground.
But dark mode done badly is worse than no dark mode at all. Here's how to do it properly.
It's Not Just "Invert the Colours"
The most common dark mode mistake is treating it as a simple palette swap — make the background dark, make the text light, done. This produces flat, amateurish interfaces that lack depth and are often harder to read than they were in light mode.
Good dark design is a different discipline from light design. The physics of how light interacts with dark surfaces is genuinely different.
The Science of Dark UI
On a white screen, everything is illuminated by the screen's backlight. Contrast comes from dark elements on a light surface.
On a dark screen, you're working with the opposite: you need to create the illusion of depth and hierarchy using elevation rather than contrast alone.
Google's Material Design documentation describes this well: in a dark UI, surfaces that are "higher" in the UI (closer to the user, more important) should be lighter than the base background. Not dramatically — a few percentage points of lightness is enough.
In practice:
:root {
--bg: #060610; /* Base — deepest surface */
--bg2: #0c0c1a; /* Secondary background */
--card: #10101f; /* Cards — one level "up" */
--border: #1a1a30; /* Subtle separators */
}
Each level is slightly lighter than the one below it. This creates visual hierarchy without relying purely on borders or drop shadows.
Contrast Ratios Are Non-Negotiable
WCAG 2.1 requires a contrast ratio of at least 4.5:1 for normal text and 3:1 for large text. In dark mode, it's easy to accidentally use greys that look readable to you on your calibrated monitor but fail entirely for users with low-contrast screens or visual impairments.
Tools to check your ratios:
- WebAIM Contrast Checker
- Chrome DevTools (Lighthouse accessibility audit)
- Figma's built-in contrast indicator
The temptation in dark mode is to use a very dim grey for secondary text. Something like #555 looks fine on your MacBook Pro with a 1000-nit display. On a cheaper Android phone, it's nearly invisible.
Our text colour hierarchy:
- Primary text:
#eaeaff— near-white, high contrast - Secondary text:
#a0aabb— softer but still readable - Muted text:
#7880a0— for metadata, captions, placeholders only
Pure Black Is Usually Wrong
#000000 for a background feels logical but creates a harsh, jarring interface. The eye struggles with extreme contrast transitions on self-illuminated screens in a way it doesn't with printed text.
Most good dark interfaces use a very dark navy, charcoal, or near-black instead:
#0a0a0f(cool, near-black with a slight blue tint — used by Vercel)#1a1a1a(neutral dark grey — used by GitHub's dark theme)#0d1117(deep blue-black — GitHub's default dark)
A slight colour tint to your base background — even just barely perceptible blue or purple — makes the interface feel more crafted and less like a system terminal from 1985.
Colour Saturation in Dark Mode
Bright, saturated colours vibrate on dark backgrounds. A #FF0000 red that looks fine on white becomes aggressive and almost painful on pure black.
The rule: desaturate your accent colours slightly for dark mode use. If your brand colour is a vivid blue, pull it back 10–15% in saturation and you'll find it reads better without losing its identity.
Compare:
#0066ff— slightly too much in dark mode#5b8ef0— same hue, reduced saturation, much more comfortable
Using CSS prefers-color-scheme
The right way to implement dark mode respects the user's system preference. CSS provides a media query for exactly this:
/* Light mode (default) */
:root {
--bg: #ffffff;
--text: #111111;
}
/* Dark mode — auto-applies when OS is set to dark */
@media (prefers-color-scheme: dark) {
:root {
--bg: #060610;
--text: #eaeaff;
}
}
You can also layer a manual toggle on top — a button that adds/removes a data-theme="dark" attribute on the element — for users who want manual control regardless of system setting.
Typography Adjustments for Dark Mode
Type reads differently on dark backgrounds. A few adjustments improve readability significantly:
- Reduce font weight slightly — a bold that looks strong on white can feel heavy on dark. Consider dropping 100 weight units (700 → 600).
- Increase letter spacing subtly — dark backgrounds reduce perceived text density; slightly loosened tracking can compensate.
- Avoid pure white for long-form text — use
#e8e8f0or similar rather than#fffffffor body copy. Reduces eye strain on long reads. - Line height — increase it by 0.05–0.1em for dark text to improve readability in low contrast environments.
The Dark Mode Checklist
Before you ship a dark UI:
- [ ] Check contrast ratios on all text combinations (4.5:1 minimum)
- [ ] Test on a non-premium display (borrow an Android phone if you're on a Mac)
- [ ] Use slightly desaturated accent colours
- [ ] Implement elevation through lightness, not just borders
- [ ] Avoid pure black backgrounds
- [ ] Respect
prefers-color-schememedia query - [ ] Test images — transparent PNGs may need dark-specific versions
- [ ] Check form elements — browser defaults often look broken in custom dark themes
- [ ] Verify link colours are identifiable without relying solely on colour
Dark mode is not a shortcut to looking technical. Done right, it's an accessibility improvement, a battery saver on OLED screens, and a signal to your users that you've thought carefully about their experience.
Done wrong, it's just a dark background with unreadable text.