Introduction
Dark mode is a display setting that uses dark backgrounds with light text and UI elements, reducing eye strain in low-light environments and potentially saving battery life on OLED displays. With over 80% of users preferring dark interfaces, implementing dark mode has become essential for modern web and mobile applications. This cheatsheet provides practical guidance for implementing robust, accessible dark mode functionality across different platforms and frameworks.
Core Concepts & Principles
Color Theory Fundamentals
- Contrast Ratios: Maintain WCAG AA compliance (4.5:1 for normal text, 3:1 for large text)
- Color Temperature: Use warmer colors in dark mode to reduce blue light exposure
- Semantic Colors: Define colors by purpose (primary, secondary, success, error) rather than specific values
- Elevation System: Use lighter shades to indicate higher elevation/depth in dark themes
Design Principles
- True Black vs Dark Gray: Use dark gray (#121212) instead of pure black (#000000) for better contrast
- Reduced Opacity: Lower opacity for disabled states and secondary content
- Brand Consistency: Maintain brand colors while adapting for dark contexts
- Progressive Enhancement: Design light mode first, then adapt for dark mode
Implementation Approaches
1. CSS Custom Properties Method
:root {
--bg-primary: #ffffff;
--text-primary: #000000;
--border-color: #e0e0e0;
}
[data-theme="dark"] {
--bg-primary: #121212;
--text-primary: #ffffff;
--border-color: #333333;
}
2. CSS Media Query Method
@media (prefers-color-scheme: dark) {
body {
background-color: #121212;
color: #ffffff;
}
}
3. Class-Based Toggle Method
.light-theme {
--bg: #ffffff;
--text: #000000;
}
.dark-theme {
--bg: #121212;
--text: #ffffff;
}
Platform-Specific Techniques
Web Development
HTML Setup
<html data-theme="light">
<head>
<meta name="color-scheme" content="light dark">
</head>
JavaScript Toggle
function toggleDarkMode() {
const html = document.documentElement;
const currentTheme = html.getAttribute('data-theme');
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
html.setAttribute('data-theme', newTheme);
localStorage.setItem('theme', newTheme);
}
System Preference Detection
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)');
const storedTheme = localStorage.getItem('theme');
function setTheme(theme) {
document.documentElement.setAttribute('data-theme', theme);
}
// Initialize theme
if (storedTheme) {
setTheme(storedTheme);
} else if (prefersDark.matches) {
setTheme('dark');
}
React Implementation
import { useState, useEffect } from 'react';
function useDarkMode() {
const [isDark, setIsDark] = useState(false);
useEffect(() => {
const stored = localStorage.getItem('darkMode');
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
setIsDark(stored ? JSON.parse(stored) : prefersDark);
}, []);
const toggle = () => {
setIsDark(!isDark);
localStorage.setItem('darkMode', JSON.stringify(!isDark));
};
return [isDark, toggle];
}
Vue.js Implementation
// Composable
export function useDarkMode() {
const isDark = ref(false);
const toggle = () => {
isDark.value = !isDark.value;
localStorage.setItem('theme', isDark.value ? 'dark' : 'light');
document.documentElement.setAttribute('data-theme', isDark.value ? 'dark' : 'light');
};
onMounted(() => {
const stored = localStorage.getItem('theme');
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
isDark.value = stored ? stored === 'dark' : prefersDark;
});
return { isDark: readonly(isDark), toggle };
}
Color Palette Templates
Basic Dark Mode Palette
| Element | Light Mode | Dark Mode | Usage |
|---|---|---|---|
| Background Primary | #FFFFFF | #121212 | Main background |
| Background Secondary | #F5F5F5 | #1E1E1E | Cards, modals |
| Text Primary | #000000 | #FFFFFF | Main content |
| Text Secondary | #666666 | #AAAAAA | Subtitles, descriptions |
| Border | #E0E0E0 | #333333 | Dividers, outlines |
| Accent | #007AFF | #0A84FF | Links, buttons |
Material Design Dark Theme
| Component | Light | Dark | Hex Dark |
|---|---|---|---|
| Surface | #FFFFFF | Surface | #121212 |
| Primary | #6200EE | Primary | #BB86FC |
| Secondary | #03DAC6 | Secondary | #03DAC6 |
| Error | #B00020 | Error | #CF6679 |
| On-Surface | #000000 | On-Surface | #FFFFFF |
Framework-Specific Solutions
Tailwind CSS
// tailwind.config.js
module.exports = {
darkMode: 'class', // or 'media'
theme: {
extend: {
colors: {
dark: {
bg: '#121212',
surface: '#1E1E1E',
text: '#FFFFFF'
}
}
}
}
}
Usage:
<div class="bg-white dark:bg-dark-bg text-black dark:text-white">
Content adapts to theme
</div>
Bootstrap Dark Mode
// Custom SCSS
$dark-bg: #121212;
$dark-text: #ffffff;
[data-bs-theme="dark"] {
--bs-body-bg: #{$dark-bg};
--bs-body-color: #{$dark-text};
}
Styled Components
const theme = {
light: {
bg: '#ffffff',
text: '#000000'
},
dark: {
bg: '#121212',
text: '#ffffff'
}
};
const Container = styled.div`
background: ${props => props.theme.bg};
color: ${props => props.theme.text};
`;
Common Challenges & Solutions
Challenge 1: Flickering on Page Load
Problem: Theme loads after initial render causing flash Solution:
// Inline script in <head>
(function() {
const theme = localStorage.getItem('theme') ||
(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
document.documentElement.setAttribute('data-theme', theme);
})();
Challenge 2: Images in Dark Mode
Problem: Images may look out of place in dark themes Solutions:
- Use CSS filters:
filter: brightness(0.8) contrast(1.2); - Provide separate dark/light image variants
- Use SVGs with currentColor for icons
Challenge 3: Third-party Components
Problem: External components don’t support dark mode Solutions:
- CSS custom properties inheritance
- CSS filters for entire component:
filter: invert(1) hue-rotate(180deg); - Component-specific overrides
Challenge 4: Browser Compatibility
Problem: prefers-color-scheme not supported in older browsers Solution:
const supportsColorScheme = window.matchMedia('(prefers-color-scheme: dark)').media !== 'not all';
if (!supportsColorScheme) {
// Fallback logic
setTheme('light');
}
Best Practices & Tips
Performance Optimization
- Use CSS custom properties for efficient theme switching
- Minimize DOM manipulation during theme changes
- Preload critical dark mode assets
- Use
color-schememeta tag for instant browser UI adaptation
Accessibility Guidelines
- Maintain contrast ratios: 4.5:1 minimum for normal text
- Test with screen readers in both modes
- Provide clear visual indicators for theme state
- Support high contrast mode preferences
User Experience
- Remember user preference across sessions
- Respect system preferences as default
- Provide smooth transitions between themes
- Add toggle animation for better feedback
Development Workflow
- Design dark mode from the start, not as an afterthought
- Use design tokens/variables for consistent theming
- Test on multiple devices and lighting conditions
- Validate color combinations for accessibility
Testing Checklist
- [ ] Theme persists across browser sessions
- [ ] No flash of unstyled content (FOUC)
- [ ] All interactive elements maintain proper contrast
- [ ] Images and media adapt appropriately
- [ ] Form inputs remain usable in both themes
- [ ] Third-party integrations work correctly
Advanced Techniques
System Theme Synchronization
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
mediaQuery.addEventListener('change', (e) => {
if (!localStorage.getItem('theme')) {
setTheme(e.matches ? 'dark' : 'light');
}
});
Smooth Theme Transitions
* {
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
}
Multiple Theme Support
const themes = {
light: { bg: '#ffffff', text: '#000000' },
dark: { bg: '#121212', text: '#ffffff' },
auto: 'system-preference'
};
Mobile App Considerations
iOS (Swift)
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
if traitCollection.userInterfaceStyle != previousTraitCollection?.userInterfaceStyle {
updateAppearance()
}
}
Android (Kotlin)
val nightModeFlags = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
when (nightModeFlags) {
Configuration.UI_MODE_NIGHT_YES -> applyDarkTheme()
Configuration.UI_MODE_NIGHT_NO -> applyLightTheme()
}
Tools & Resources
Development Tools
- Chrome DevTools: Test prefers-color-scheme in rendering tab
- Firefox DevTools: Simulate dark mode in responsive design mode
- Figma: Design system templates with dark mode variants
- Storybook: Test components in multiple themes
Color Tools
- Contrast Checker: WebAIM Contrast Checker for accessibility
- Coolors.co: Generate dark mode palettes
- Material Design: Color tool with dark theme support
- Adobe Color: Extract colors from images for theming
Testing Resources
- axe-core: Automated accessibility testing
- Lighthouse: Audit for color contrast issues
- Color Oracle: Simulate color blindness
- High Contrast Extensions: Test extreme contrast scenarios
Libraries & Frameworks
- next-themes: React theme management
- use-dark-mode: React hook for dark mode
- vue-dark-mode: Vue.js dark mode plugin
- styled-theming: Styled-components theming utility
Documentation & Guides
- MDN Web Docs: CSS
prefers-color-scheme - Web.dev: Building a color scheme
- Apple Human Interface Guidelines: Dark Mode
- Material Design: Dark theme specification
Quick Reference Commands
CSS Variables Setup
:root {
--color-bg: light;
--color-text: dark;
}
@media (prefers-color-scheme: dark) {
:root {
--color-bg: dark;
--color-text: light;
}
}
JavaScript Theme Toggle
const toggle = () => {
const theme = document.documentElement.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
document.documentElement.setAttribute('data-theme', theme);
localStorage.setItem('theme', theme);
};
React Hook Pattern
const [theme, setTheme] = useState('light');
useEffect(() => {
document.documentElement.setAttribute('data-theme', theme);
}, [theme]);
This comprehensive cheatsheet covers all essential aspects of dark mode implementation, from basic concepts to advanced techniques, ensuring you can create professional, accessible dark mode experiences across any platform or framework.
