Dark Mode in Modern Web Development
Dark mode has become an increasingly important feature in web design. Many users prefer dark mode for reduced eye strain, better battery life on OLED screens, and aesthetic preferences. Modern operating systems and browsers provide dark mode support, and many websites now offer dark mode options.
Implementing dark mode effectively requires more than simply inverting colors. You need to carefully manage colors to ensure readability, maintain contrast, preserve brand identity, and provide a cohesive experience across light and dark themes.
Why Implement Dark Mode
Before diving into implementation, understand why dark mode matters.
User preference is significant. Studies show that many users prefer dark mode, especially for evening use or in low-light environments. Providing dark mode aligns with user expectations.
Accessibility benefits: Dark mode can improve readability for people with light sensitivity or certain visual conditions. Reduced brightness on OLED screens also helps users with light-sensitive conditions.
Device battery life: OLED screens consume less power displaying dark colors (because each pixel produces its own light). Dark mode can meaningfully extend battery life on mobile devices with OLED screens.
Brand consistency: Many brands now have dark mode versions of their websites and applications. Providing dark mode helps maintain consistency across touchpoints.
Understanding Prefers-Color-Scheme
The CSS media query prefers-color-scheme allows you to detect whether the user prefers light or dark mode based on their operating system or browser settings.
@media (prefers-color-scheme: light) {
/* Styles for light mode */
}
@media (prefers-color-scheme: dark) {
/* Styles for dark mode */
}
The user's preference typically comes from their operating system settings (light mode or dark mode in Windows, macOS, or iOS) or browser settings.
Always provide default styles (assuming light mode) and then override them in the dark mode media query. This ensures compatibility with older browsers that don't support prefers-color-scheme.
Color Management Strategies for Dark Mode
CSS Custom Properties (Variables)
The most effective approach to managing colors for multiple themes is using CSS custom properties.
Define color variables that apply to both light and dark modes:
:root {
/* Light mode (default) */
--bg-primary: #ffffff;
--bg-secondary: #f5f5f5;
--text-primary: #1a1a1a;
--text-secondary: #666666;
--border-color: #dddddd;
--accent-color: #007bff;
}
@media (prefers-color-scheme: dark) {
:root {
/* Dark mode */
--bg-primary: #1a1a1a;
--bg-secondary: #2d2d2d;
--text-primary: #ffffff;
--text-secondary: #cccccc;
--border-color: #444444;
--accent-color: #4da6ff;
}
}
Then use these variables throughout your stylesheet:
body {
background-color: var(--bg-primary);
color: var(--text-primary);
}
.card {
background-color: var(--bg-secondary);
border: 1px solid var(--border-color);
}
button {
background-color: var(--accent-color);
color: var(--text-primary);
}
This approach provides several benefits. All color changes are centralized in one place. Adding a new color only requires defining it in both light and dark mode sections. Updating a color affects all elements using that variable. Themes can be easily extended with additional variables.
HSL for Flexible Color Adjustment
Using HSL color format makes it easier to create dark mode variants that maintain color relationships.
For example, in light mode you might use:
--accent-color: hsl(210, 100%, 50%);
In dark mode, you might adjust the lightness:
--accent-color: hsl(210, 100%, 65%);
This keeps the same hue and saturation while adjusting brightness for dark mode. The color remains visually related to the light mode color but is appropriately bright for dark backgrounds.
Implementing Dark Mode Step by Step
Step 1: Define Your Color Palette
Start by defining complete color palettes for both light and dark modes. Consider:
Background colors (primary and secondary) Text colors (primary, secondary, and disabled) Border and divider colors Interactive element colors (buttons, links) Accent and highlight colors Status colors (success, error, warning, info)
Ensure all text colors meet WCAG contrast requirements against their background colors in both themes.
Step 2: Create CSS Variables
Structure your CSS variables hierarchically:
:root {
/* Base colors */
--color-gray-50: #f9fafb;
--color-gray-100: #f3f4f6;
--color-gray-900: #111827;
--color-blue-500: #3b82f6;
--color-blue-600: #2563eb;
/* Semantic colors */
--bg-primary: var(--color-gray-50);
--bg-secondary: #ffffff;
--text-primary: var(--color-gray-900);
--text-secondary: #666666;
}
@media (prefers-color-scheme: dark) {
:root {
--bg-primary: var(--color-gray-900);
--bg-secondary: #1f2937;
--text-primary: var(--color-gray-50);
--text-secondary: #d1d5db;
}
}
Step 3: Apply Variables Throughout Stylesheets
Use variables consistently in all stylesheets:
body {
background-color: var(--bg-primary);
color: var(--text-primary);
}
a {
color: var(--color-blue-500);
}
a:hover {
color: var(--color-blue-600);
}
.button-primary {
background-color: var(--color-blue-500);
color: white;
}
.button-primary:hover {
background-color: var(--color-blue-600);
}
.card {
background-color: var(--bg-secondary);
border: 1px solid var(--border-color);
}
input {
background-color: var(--bg-secondary);
color: var(--text-primary);
border: 1px solid var(--border-color);
}
input::placeholder {
color: var(--text-secondary);
}
Step 4: Test Thoroughly
Test your implementation across different browsers and devices:
Verify that both light and dark modes render correctly. Test in actual dark mode on your OS (not just the browser's dark mode simulation). Verify contrast requirements in both themes. Check that images and graphics look appropriate in both themes. Test interactive elements (hover, focus, active states) in both themes. Test with different browser zoom levels. Test with Windows High Contrast Mode enabled.
Step 5: Handle Images and Media
Some content might need different versions for light and dark modes.
For images, consider using CSS filter properties:
@media (prefers-color-scheme: dark) {
img.logo {
filter: brightness(0) invert(1);
}
}
Or provide different images:
<picture>
<source srcset="logo-dark.svg" media="(prefers-color-scheme: dark)">
<img src="logo-light.svg" alt="Company Logo">
</picture>
For icons and SVGs, use CSS filters or provide alternate versions.
Allowing User Override
Beyond system preference, many users appreciate the ability to manually toggle between light and dark modes.
You can detect the user's preference and allow overriding it:
// Check if user has a saved preference
const savedMode = localStorage.getItem('color-scheme');
// Check system preference
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
// Set mode: user preference takes precedence over system preference
const mode = savedMode || (prefersDark ? 'dark' : 'light');
// Apply mode to document
document.documentElement.setAttribute('data-color-scheme', mode);
// Toggle button handler
document.getElementById('theme-toggle').addEventListener('click', () => {
const currentMode = document.documentElement.getAttribute('data-color-scheme');
const newMode = currentMode === 'dark' ? 'light' : 'dark';
document.documentElement.setAttribute('data-color-scheme', newMode);
localStorage.setItem('color-scheme', newMode);
});
Then update CSS to respect the data attribute:
/* Default to system preference */
/* Light mode styles here */
@media (prefers-color-scheme: dark) {
/* Dark mode styles here */
}
/* Allow user override */
[data-color-scheme="light"] {
--bg-primary: #ffffff;
--text-primary: #1a1a1a;
/* light mode colors */
}
[data-color-scheme="dark"] {
--bg-primary: #1a1a1a;
--text-primary: #ffffff;
/* dark mode colors */
}
Color Accessibility in Both Modes
Ensure colors meet accessibility requirements in both light and dark modes:
Check contrast ratios in both themes. Test with color blindness simulators in both themes. Avoid relying on color alone to convey information in either theme. Ensure interactive elements are clearly distinct in both themes. Test with different vision types in both themes.
Common Pitfalls to Avoid
Don't simply invert colors. Inverted light mode colors often don't create acceptable dark mode colors. Colors need to be carefully selected for each theme.
Don't ignore images. Images that look good on light backgrounds might need adjustment for dark backgrounds.
Don't forget about transitions. Users switching themes might experience jarring changes. Consider using CSS transitions:
:root {
transition: background-color 0.3s ease, color 0.3s ease;
}
Don't forget intermediate tones. When creating dark mode, ensure you have appropriate colors for disabled states, hover states, and other variants.
Don't assume everyone wants dark mode. Some users find dark mode harder to read. Respecting the system preference means respecting the user's choice.
Performance Considerations
Dark mode implementation should not significantly impact performance:
Use CSS custom properties efficiently. They have minimal performance impact. Avoid excessive media queries. Structure them logically. Preload dark mode images/assets if users frequently switch themes. Use CSS transitions sparingly to avoid performance issues during switching.
Implementing dark mode effectively requires thoughtful color management, careful testing, and attention to accessibility. By using CSS variables to manage colors systematically and testing thoroughly in both light and dark modes, you can provide users with a high-quality dark mode experience that maintains your brand identity and ensures readability across all viewing conditions.


