Thomas Kim
Implementing Dark Mode in React
Dark mode has become an essential feature for modern web applications. Let’s explore how to implement it properly with React.
Why Dark Mode Matters
- Reduces eye strain in low-light environments
- Saves battery on OLED screens
- User preference and accessibility
- Modern, polished feel
CSS Variables Example
Define theme colors as CSS variables:
:root {
--bg-primary: #ffffff;
--bg-secondary: #f3f4f6;
--text-primary: #111827;
--text-secondary: #6b7280;
--border: #d1d5db;
}
[data-theme='dark'] {
--bg-primary: #1f2937;
--bg-secondary: #374151;
--text-primary: #f3f4f6;
--text-secondary: #9ca3af;
--border: #4b5563;
}
body {
background-color: var(--bg-primary);
color: var(--text-primary);
transition: background-color 0.3s ease, color 0.3s ease;
}React Context Implementation
import { createContext, useContext, useState, useEffect } from 'react'
const ThemeContext = createContext()
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light')
useEffect(() => {
// Load saved theme
const savedTheme = localStorage.getItem('theme')
if (savedTheme) {
setTheme(savedTheme)
} else {
// Use system preference
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
setTheme(prefersDark ? 'dark' : 'light')
}
}, [])
useEffect(() => {
// Apply theme to document
document.documentElement.setAttribute('data-theme', theme)
localStorage.setItem('theme', theme)
}, [theme])
const toggleTheme = () => {
setTheme(prev => prev === 'light' ? 'dark' : 'light')
}
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
)
}
export function useTheme() {
const context = useContext(ThemeContext)
if (!context) {
throw new Error('useTheme must be used within ThemeProvider')
}
return context
}Usage in Components
function Header() {
const { theme, toggleTheme } = useTheme()
return (
<header>
<h1>My App</h1>
<button onClick={toggleTheme}>
{theme === 'light' ? '🌙' : '☀️'}
</button>
</header>
)
}Detecting System Preference
Listen to system theme changes:
useEffect(() => {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
const handleChange = (e) => {
if (e.matches) {
setTheme('dark')
} else {
setTheme('light')
}
}
mediaQuery.addEventListener('change', handleChange)
return () => mediaQuery.removeEventListener('change', handleChange)
}, [])Preventing Flash of Unstyled Content
Add a script in your HTML <head> to set theme before render:
<script>
(function() {
const theme = localStorage.getItem('theme') ||
(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
document.documentElement.setAttribute('data-theme', theme)
})()
</script>TailwindCSS Dark Mode
If using Tailwind, enable dark mode in config:
// tailwind.config.js
module.exports = {
darkMode: 'class', // or 'media' for system preference
// ... other config
}Usage:
<div className="bg-white dark:bg-gray-900 text-black dark:text-white">
This text changes color based on theme
</div>Best Practices
- Respect user preference - Check system settings first
- Persist choice - Save to localStorage
- Smooth transitions - Use CSS transitions for theme changes
- Test contrast ratios - Ensure accessibility in both modes
- Consider images - Provide different versions for each theme if needed
Conclusion
Dark mode is more than just inverting colors. A thoughtful implementation respects user preferences, maintains accessibility, and enhances the overall user experience!
Related
Jan 28, 2025
Jan 12, 2025