--- name: dark-mode-implementer description: Implements complete dark/light mode theming systems using CSS variables, Tailwind dark mode, React context, and system preference detection. Use when users request "add dark mode", "theme toggle", "dark theme", "light mode switch", or "color scheme". --- # Dark Mode Implementer Build robust dark/light mode theming with system preference detection and persistent storage. ## Core Workflow 1. **Choose strategy**: CSS-only, Tailwind, or React context 2. **Define color tokens**: Create semantic color variables 3. **Implement toggle**: Add theme switch component 4. **Detect system preference**: Respect `prefers-color-scheme` 5. **Persist choice**: Store preference in localStorage 6. **Prevent flash**: Handle initial load correctly ## Strategy Comparison | Strategy | Best For | Complexity | |----------|----------|------------| | Tailwind `class` | React/Vue/Svelte apps | Low | | CSS `media` | Simple static sites | Very Low | | CSS Variables + JS | Framework-agnostic | Medium | | React Context | Complex React apps | Medium | ## Tailwind CSS Dark Mode ### Enable Class Strategy ```javascript // tailwind.config.js module.exports = { darkMode: 'class', // or 'media' for system-only theme: { extend: { colors: { // Semantic color tokens background: 'hsl(var(--background))', foreground: 'hsl(var(--foreground))', primary: 'hsl(var(--primary))', muted: 'hsl(var(--muted))', }, }, }, }; ``` ### CSS Variables Setup ```css /* globals.css */ @tailwind base; @tailwind components; @tailwind utilities; @layer base { :root { --background: 0 0% 100%; --foreground: 222 47% 11%; --primary: 221 83% 53%; --primary-foreground: 210 40% 98%; --secondary: 210 40% 96%; --secondary-foreground: 222 47% 11%; --muted: 210 40% 96%; --muted-foreground: 215 16% 47%; --accent: 210 40% 96%; --accent-foreground: 222 47% 11%; --destructive: 0 84% 60%; --destructive-foreground: 210 40% 98%; --border: 214 32% 91%; --input: 214 32% 91%; --ring: 221 83% 53%; --radius: 0.5rem; } .dark { --background: 222 47% 11%; --foreground: 210 40% 98%; --primary: 217 91% 60%; --primary-foreground: 222 47% 11%; --secondary: 217 33% 17%; --secondary-foreground: 210 40% 98%; --muted: 217 33% 17%; --muted-foreground: 215 20% 65%; --accent: 217 33% 17%; --accent-foreground: 210 40% 98%; --destructive: 0 62% 30%; --destructive-foreground: 210 40% 98%; --border: 217 33% 17%; --input: 217 33% 17%; --ring: 224 76% 48%; } } @layer base { * { @apply border-border; } body { @apply bg-background text-foreground; } } ``` ### Using Dark Mode Classes ```html

Title

Description

``` ## React Theme Provider ### Complete Theme Context ```tsx // lib/theme-context.tsx 'use client'; import { createContext, useContext, useEffect, useState } from 'react'; type Theme = 'light' | 'dark' | 'system'; interface ThemeContextType { theme: Theme; setTheme: (theme: Theme) => void; resolvedTheme: 'light' | 'dark'; } const ThemeContext = createContext(undefined); const STORAGE_KEY = 'theme'; export function ThemeProvider({ children }: { children: React.ReactNode }) { const [theme, setThemeState] = useState('system'); const [resolvedTheme, setResolvedTheme] = useState<'light' | 'dark'>('light'); const [mounted, setMounted] = useState(false); // Get system preference const getSystemTheme = (): 'light' | 'dark' => { if (typeof window === 'undefined') return 'light'; return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; }; // Apply theme to document const applyTheme = (theme: Theme) => { const root = document.documentElement; const resolved = theme === 'system' ? getSystemTheme() : theme; root.classList.remove('light', 'dark'); root.classList.add(resolved); setResolvedTheme(resolved); }; // Set theme and persist const setTheme = (newTheme: Theme) => { setThemeState(newTheme); localStorage.setItem(STORAGE_KEY, newTheme); applyTheme(newTheme); }; // Initialize theme on mount useEffect(() => { const stored = localStorage.getItem(STORAGE_KEY) as Theme | null; const initialTheme = stored || 'system'; setThemeState(initialTheme); applyTheme(initialTheme); setMounted(true); }, []); // Listen for system preference changes useEffect(() => { const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); const handleChange = () => { if (theme === 'system') { applyTheme('system'); } }; mediaQuery.addEventListener('change', handleChange); return () => mediaQuery.removeEventListener('change', handleChange); }, [theme]); // Prevent hydration mismatch if (!mounted) { return <>{children}; } return ( {children} ); } export function useTheme() { const context = useContext(ThemeContext); if (!context) { throw new Error('useTheme must be used within a ThemeProvider'); } return context; } ``` ### Prevent Flash of Wrong Theme ```tsx // app/layout.tsx import { ThemeProvider } from '@/lib/theme-context'; // Inline script to prevent flash const themeScript = ` (function() { const stored = localStorage.getItem('theme'); const theme = stored || 'system'; const resolved = theme === 'system' ? window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light' : theme; document.documentElement.classList.add(resolved); })(); `; export default function RootLayout({ children }: { children: React.ReactNode }) { return (