Why Your Dark Mode Implementation Sucks (And How to Fix It)
Most dark modes are just inverted colors that hurt your eyes. Here's how to build one that users actually want to keep enabled.

You've seen it everywhere. That jarring flash of white when switching to dark mode. The washed-out grays that make text barely readable. The neon blues that feel like staring into a flashlight.
I've shipped dark mode features for three different products over the past two years, and I learned the hard way that flipping colors isn't enough. Users will enable your dark mode once, squint at the screen for thirty seconds, then switch back to light mode forever.
The Problem with "Flip Everything Dark"
Most developers (myself included, initially) approach dark mode like this:
/* The wrong way */
[data-theme="dark"] {
--background: #000000;
--text: #ffffff;
--border: #333333;
}This creates what I call "inverted color syndrome." Pure black backgrounds cause eye strain because of the extreme contrast. White text on pure black creates a halation effect where the text appears to glow and blur slightly.
I noticed this when testing the dashboard I built for a fintech client. Users complained that the dark mode "felt aggressive" and made them tired faster. The problem wasn't the concept - it was the execution.
Understanding Color Temperature and Perception
Dark mode isn't about making things dark. It's about reducing luminance while maintaining readability and visual hierarchy.
The key insight: your eye doesn't process "dark" colors the same way in different lighting conditions. What looks like a subtle gray in bright office lighting becomes invisible on a dim evening screen.
I started testing my interfaces in three scenarios: - Bright office (overhead fluorescents) - Normal indoor lighting - Dark room with just screen light
The color combinations that worked in all three were rarely pure blacks and whites.
Building a Proper Dark Palette
Here's the system I use now:
:root {
/* Light mode */
--surface-1: #ffffff;
--surface-2: #f8f9fa;
--surface-3: #e9ecef;
--text-primary: #212529;
--text-secondary: #6c757d;[data-theme="dark"] {
/ Dark mode - notice these aren't inversions /
--surface-1: #1a1a1a;
--surface-2: #2d2d2d;
--surface-3: #404040;
--text-primary: #e0e0e0;
--text-secondary: #a0a0a0;
}
`
The magic is in the relationships, not the individual colors. Your darkest background should never be pure black (#000000). I typically start with #1a1a1a or #121212.
For text, pure white (#ffffff) creates too much contrast. #e0e0e0 or #f0f0f0 gives you excellent readability without the eye strain.
Handling the Theme Toggle Transition
The technical implementation matters as much as the colors. Users notice janky transitions, and they'll blame your entire dark mode for it.
// Add this to prevent flash of wrong theme
const getInitialTheme = () => {
if (typeof window !== 'undefined' && window.localStorage) {
const storedPrefs = window.localStorage.getItem('theme');
if (typeof storedPrefs === 'string') {
return storedPrefs;
}
const userMedia = window.matchMedia('(prefers-color-scheme: dark)');
if (userMedia.matches) {
return 'dark';
}
}
return 'light';// Smooth transition between themes
const toggleTheme = () => {
// Prevent transition during initial load
document.documentElement.classList.add('theme-transition');
const newTheme = theme === 'light' ? 'dark' : 'light';
setTheme(newTheme);
localStorage.setItem('theme', newTheme);
// Remove transition class after animation completes
setTimeout(() => {
document.documentElement.classList.remove('theme-transition');
}, 200);
};
`
.theme-transition * {
transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease !important;
}I learned this technique after users complained about the "flashing" effect when switching themes. The transition class prevents the jarring snap between color schemes.
The Devil in the Details
The difference between good and great dark mode lies in handling edge cases:
Images and media: Don't just leave them as-is. Consider adding a subtle overlay or adjusting opacity.
[data-theme="dark"] img:not([data-theme-ignore]) {
opacity: 0.8;
transition: opacity 0.2s ease;[data-theme="dark"] img:not([data-theme-ignore]):hover {
opacity: 1;
}
`
Shadows and borders: Light mode shadows don't work in dark mode. You need different approaches.
.card {
/* Light mode */
border: 1px solid #e0e0e0;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);[data-theme="dark"] .card {
/ Dark mode - lighter borders, different shadow approach /
border: 1px solid #404040;
box-shadow: 0 2px 4px rgba(0,0,0,0.3);
}
`
Interactive elements: Hover and focus states need recalibrating. What looks like a subtle highlight in light mode might be invisible in dark mode.
Testing Your Implementation
I use this checklist for every dark mode I ship:
- [ ] Test in actual dark room conditions (not just dimmed office)
- [ ] Verify all text meets WCAG contrast requirements (use WebAIM's contrast checker)
- [ ] Check hover states on interactive elements
- [ ] Test form inputs and their placeholder text
- [ ] Verify loading states and skeleton screens work
- [ ] Test with actual user content (not just lorem ipsum)
The "actual user content" point is crucial. Your carefully chosen colors might work perfectly with your sample data but break when users add their own purple profile pictures or bright yellow highlighting.
What You Can Do Today
- Audit your current dark mode in three different lighting conditions
- Replace any pure black (#000000) backgrounds with dark grays (#1a1a1a)
- Add smooth transitions between theme switches
- Test your contrast ratios with an automated tool
- Ask three people to actually use your dark mode for real tasks
The last point matters most. I've seen too many dark modes that look great in design tools but fail when people try to read actual content or fill out real forms.
Dark mode done right feels effortless - users don't think about it, they just appreciate that their eyes don't hurt during late-night coding sessions. That's the implementation worth shipping.

Ibrahim Lawal
Full-Stack Developer & AI Integration Specialist. Building AI-powered products that solve real problems.
View Portfolio