Learn how to easily add dark mode to your web app for a sleek, user-friendly experience with our step-by-step guide.

Book a call with an Expert
Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.
Why Dark Mode Matters
Dark mode isn't just a trendy feature—it's a genuine usability enhancement that your users increasingly expect. Beyond aesthetic preferences, dark mode reduces eye strain in low-light environments, extends battery life on OLED displays, and provides accessibility benefits for users with certain visual sensitivities.
Implementation Strategy: The 5-Layer Approach
Let me walk you through implementing dark mode in a way that's both technically sound and business-friendly. I've broken this down into a layered approach that works for both new projects and existing applications.
Create a color abstraction layer
Before writing a single line of code, revisit your color system. The key is to stop thinking in terms of literal colors and start thinking in terms of semantic variables.
/* Don't do this */
.button {
background-color: #3366ff;
color: white;
}
/* Do this instead */
:root {
--color-primary: #3366ff;
--color-text-on-primary: white;
--color-background: #ffffff;
--color-text: #333333;
}
.button {
background-color: var(--color-primary);
color: var(--color-text-on-primary);
}
body {
background-color: var(--color-background);
color: var(--color-text);
}
--color-surface or --color-border-subtle
Create a user preference system
Next, implement the mechanism for users to switch between modes. There are three standard approaches:
// Method 1: Simple toggle with localStorage persistence
function toggleDarkMode() {
// Toggle the dark-mode class on the document
document.documentElement.classList.toggle('dark-mode');
// Store user preference
const isDarkMode = document.documentElement.classList.contains('dark-mode');
localStorage.setItem('darkMode', isDarkMode);
}
// Method 2: Respect system preferences with prefers-color-scheme
function initializeTheme() {
// Check if user has previously set a preference
const savedPreference = localStorage.getItem('darkMode');
if (savedPreference !== null) {
// Apply saved preference
document.documentElement.classList.toggle('dark-mode', savedPreference === 'true');
} else {
// Fall back to system preference
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
document.documentElement.classList.toggle('dark-mode', prefersDark);
}
}
// Call this function when your app initializes
initializeTheme();
Auto-detection with manual override
The most sophisticated approach combines system detection with user choice:
// Complete theme system with three options: light, dark, or system
function setThemePreference(theme) {
// Remove any existing theme classes
document.documentElement.classList.remove('light-mode', 'dark-mode');
if (theme === 'system') {
// Detect system preference
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
document.documentElement.classList.add(prefersDark ? 'dark-mode' : 'light-mode');
} else {
// Apply explicit user choice
document.documentElement.classList.add(theme + '-mode');
}
// Save preference
localStorage.setItem('themePreference', theme);
}
// Listen for system preference changes
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
const currentPreference = localStorage.getItem('themePreference');
if (currentPreference === 'system') {
document.documentElement.classList.toggle('dark-mode', event.matches);
document.documentElement.classList.toggle('light-mode', !event.matches);
}
});
Two approaches to theme switching
There are two main patterns for implementing the theme CSS:
/* Approach 1: Class-based theme switching */
:root {
/* Light theme (default) */
--color-background: #ffffff;
--color-surface: #f5f5f5;
--color-text: #333333;
--color-primary: #3366ff;
--color-border: #e0e0e0;
--color-shadow: rgba(0, 0, 0, 0.1);
}
.dark-mode {
/* Dark theme */
--color-background: #121212;
--color-surface: #1e1e1e;
--color-text: #e0e0e0;
--color-primary: #6699ff; /* Slightly lighter blue for dark backgrounds */
--color-border: #333333;
--color-shadow: rgba(0, 0, 0, 0.3);
}
/* Approach 2: Media query approach */
:root {
/* Light theme variables */
--color-background: #ffffff;
--color-text: #333333;
/* More variables... */
}
@media (prefers-color-scheme: dark) {
:root {
/* Dark theme variables */
--color-background: #121212;
--color-text: #e0e0e0;
/* More variables... */
}
}
Best practice: Use the class-based approach for most projects as it gives you more control and allows for user preferences to override system settings.
Images and illustrations
Dark mode isn't just about changing text and background colors. Your visual assets need attention too:
<!-- Method 1: Using the <picture> element -->
<picture>
<source srcset="/images/logo-dark.png" media="(prefers-color-scheme: dark)">
<img src="/images/logo-light.png" alt="Company Logo">
</picture>
<!-- Method 2: CSS-based switching for background images -->
<style>
.hero-image {
background-image: url('/images/hero-light.jpg');
}
.dark-mode .hero-image {
background-image: url('/images/hero-dark.jpg');
}
</style>
SVG considerations
SVGs are particularly well-suited for dark mode because you can adjust their colors using CSS:
.icon-path {
fill: var(--color-icon);
}
/* For multi-color SVGs */
.icon-primary {
fill: var(--color-icon-primary);
}
.icon-secondary {
fill: var(--color-icon-secondary);
}
Third-party components
Many UI libraries now offer dark mode versions, but for those that don't:
/* Example: Overriding a third-party date picker in dark mode */
.dark-mode .datepicker-container {
background-color: var(--color-surface);
border-color: var(--color-border);
}
.dark-mode .datepicker-day {
color: var(--color-text);
}
.dark-mode .datepicker-selected {
background-color: var(--color-primary);
color: white;
}
Smooth transitions between modes
Avoid jarring transitions when switching themes:
/* Add a subtle transition for theme switching */
body {
transition: background-color 0.3s ease, color 0.3s ease;
}
/* Be careful not to transition everything, which can hurt performance */
* {
/* Don't do this - it's too heavy */
transition: all 0.3s ease;
}
Prevent flash of incorrect theme
A common issue is the "flash of incorrect theme" when the page loads:
<!-- Add this script in the <head> before any CSS -->
<script>
// Immediately set the initial theme to prevent flash
(function() {
const savedTheme = localStorage.getItem('themePreference');
if (savedTheme === 'dark') {
document.documentElement.classList.add('dark-mode');
} else if (savedTheme === 'system' || savedTheme === null) {
// Check system preference
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.documentElement.classList.add('dark-mode');
}
}
})();
</script>
Incremental implementation
You don't need to transform your entire application at once:
ROI of dark mode
Dark mode delivers tangible benefits that translate to business value:
Testing considerations
A solid QA strategy for dark mode should include:
Not just inverting colors
The most common mistake is treating dark mode as a simple inversion of light mode. In reality:
Forgetting about images and embedded content
Many developers focus only on CSS variables but forget about:
Ignoring design fundamentals
Theme switching without page refresh
For single-page applications, implement theme switching without disrupting the user's flow:
// Framework-agnostic theme context for component-level awareness
class ThemeManager {
constructor() {
this.listeners = [];
this.currentTheme = localStorage.getItem('themePreference') || 'system';
// Initialize theme on first load
this.applyTheme(this.currentTheme);
// Listen for system changes
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
if (this.currentTheme === 'system') {
this.notifyListeners(this.getEffectiveTheme());
}
});
}
applyTheme(theme) {
this.currentTheme = theme;
localStorage.setItem('themePreference', theme);
const effectiveTheme = this.getEffectiveTheme();
document.documentElement.classList.remove('light-mode', 'dark-mode');
document.documentElement.classList.add(effectiveTheme + '-mode');
this.notifyListeners(effectiveTheme);
}
getEffectiveTheme() {
if (this.currentTheme === 'system') {
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
return this.currentTheme;
}
subscribe(callback) {
this.listeners.push(callback);
return () => {
this.listeners = this.listeners.filter(listener => listener !== callback);
};
}
notifyListeners(theme) {
this.listeners.forEach(listener => listener(theme));
}
}
// Create a singleton instance
const themeManager = new ThemeManager();
export default themeManager;
Framework-specific implementations
Here's how you might implement dark mode in React:
// React Context for theme management
import React, { createContext, useContext, useEffect, useState } from 'react';
const ThemeContext = createContext();
export const ThemeProvider = ({ children }) => {
const [theme, setTheme] = useState(() => {
const saved = localStorage.getItem('themePreference');
if (saved) return saved;
return 'system';
});
const [effectiveTheme, setEffectiveTheme] = useState('light');
useEffect(() => {
// Apply theme changes to document
if (theme === 'system') {
const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
setEffectiveTheme(isDark ? 'dark' : 'light');
} else {
setEffectiveTheme(theme);
}
document.documentElement.classList.remove('dark-mode', 'light-mode');
document.documentElement.classList.add(`${effectiveTheme}-mode`);
localStorage.setItem('themePreference', theme);
}, [theme, effectiveTheme]);
// Listen for system changes
useEffect(() => {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
const handleChange = () => {
if (theme === 'system') {
setEffectiveTheme(mediaQuery.matches ? 'dark' : 'light');
}
};
mediaQuery.addEventListener('change', handleChange);
return () => mediaQuery.removeEventListener('change', handleChange);
}, [theme]);
return (
<ThemeContext.Provider value={{ theme, setTheme, effectiveTheme }}>
{children}
</ThemeContext.Provider>
);
};
// Custom hook for components to use
export const useTheme = () => useContext(ThemeContext);
Dark mode is more than a binary switch between light and dark—it's an opportunity to rethink how your application presents information and interacts with users in different contexts.
The most successful dark mode implementations don't just change colors; they consider the entire user experience, from readability to performance to brand consistency.
By taking a systematic approach with CSS variables, thoughtful component design, and user preference management, you can create a dark mode experience that truly enhances your application rather than just checking a feature box.
Remember that dark mode isn't the end of the journey—it's the beginning of thinking about your application as an adaptable, context-aware experience that meets users where they are.
Explore the top 3 practical dark mode use cases to enhance your web app’s user experience and style.
From startups to enterprises and everything in between, see for yourself our incredible impact.
Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We’ll discuss your project and provide a custom quote at no cost.Â