# Theme Implementation Guide: Dark and Light Themes ## Overview This guide outlines critical implementation patterns for dark and light themes using next-themes in Next.js applications, based on best practices and real-world implementation experience. ## Core Architecture Principles ### 1. CSS Variable Strategy (Primary Approach) **ALWAYS USE**: CSS variables with semantic naming for theme switching **AVOID**: Inline `dark:` classes throughout components ```css :root { /* Semantic color variables - light theme defaults */ --color-text: #1e293b; --color-text-light: #64748b; --color-bg: #ffffff; --color-bg-secondary: #f8fafc; --color-primary: #1a56db; --color-border: #e2e8f0; } .dark { /* Dark theme overrides */ --color-text: #f3f4f6; --color-text-light: #e5e7eb; --color-bg: #0f172a; --color-bg-secondary: #1e293b; --color-primary: #60a5fa; --color-border: #475569; } ``` ### 2. Component Class Pattern Create semantic CSS classes that reference variables: ```css .card { background-color: var(--color-bg-secondary); color: var(--color-text); border: 1px solid var(--color-border); } .card-title { color: var(--color-text); } .card-description { color: var(--color-text-light); } ``` **Component Usage**: ```jsx // ✅ CORRECT - Uses semantic classes

Title

Description

// ❌ AVOID - Inline dark: classes

Title

Description

``` ## next-themes Implementation Checklist ### 1. Essential Setup ```jsx // app/layout.tsx import { ThemeProvider } from 'next-themes' export default function Layout({ children }) { return ( {children} ) } ``` ### 2. Theme Toggle Component ```jsx 'use client' import { useTheme } from 'next-themes' import { useEffect, useState } from 'react' export function ThemeToggle() { const [mounted, setMounted] = useState(false) const { theme, setTheme } = useTheme() // Prevent hydration mismatch useEffect(() => setMounted(true), []) if (!mounted) return null // Critical for SSR return ( ) } ``` ## Critical Implementation Rules ### 1. NEVER Access Theme During SSR ```jsx // ❌ WRONG - Causes hydration mismatch function Component() { const { theme } = useTheme() return (
Content
) } // ✅ CORRECT - Use CSS variables only function Component() { return (
Content
) } ``` ### 2. Always Check Mount State ```jsx // ✅ CORRECT Pattern function ThemeAwareComponent() { const [mounted, setMounted] = useState(false) const { theme } = useTheme() useEffect(() => setMounted(true), []) if (!mounted) { return
// Fallback UI } // Safe to use theme here return
Theme: {theme}
} ``` ### 3. CSS Variable Hierarchy Structure variables from general to specific: ```css :root { /* 1. Brand Colors (theme-independent) */ --brand-primary: #1a56db; --brand-secondary: #7e3af2; /* 2. Semantic Colors (theme-dependent) */ --color-text: #1e293b; --color-text-light: #64748b; --color-text-muted: #94a3b8; /* 3. Component Colors (derived from semantic) */ --card-bg: var(--color-bg-secondary); --card-text: var(--color-text); --card-border: var(--color-border); /* 4. State Colors */ --color-success: #059669; --color-warning: #d97706; --color-error: #dc2626; } ``` ## Accessibility Requirements ### 1. Contrast Ratios Ensure WCAG AA compliance (4.5:1 for normal text, 3:1 for large text): ```css :root { --color-text: #1e293b; /* 16.8:1 ratio on white */ --color-text-light: #64748b; /* 7.2:1 ratio on white */ } .dark { --color-text: #f3f4f6; /* 17.4:1 ratio on dark bg */ --color-text-light: #e5e7eb; /* 13.8:1 ratio on dark bg */ } ``` ### 2. Focus Management ```css *:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--color-primary); } .dark *:focus-visible { box-shadow: 0 0 0 3px var(--color-primary-light); } ``` ### 3. Reduced Motion Support ```css @media (prefers-reduced-motion: reduce) { .card { transition: none; } } ``` ## Common Pitfalls & Solutions ### 1. Flash of Unstyled Content (FOUC) **Problem**: Theme flashes on page load **Solution**: Use `suppressHydrationWarning` and CSS-only theme switching ### 2. Hydration Mismatches **Problem**: Server renders light theme, client uses dark theme **Solution**: Never conditionally render based on theme in components ### 3. CSS Specificity Issues **Problem**: `dark:` classes not applying **Solution**: Use CSS variables and `.dark` attribute selectors ### 4. Performance Issues **Problem**: Re-rendering on theme change **Solution**: CSS-only theme switching, avoid theme-dependent useEffect ## Testing Checklist ### 1. Theme Switching - [ ] Light to dark transition works smoothly - [ ] Dark to light transition works smoothly - [ ] System preference detection works - [ ] Theme persists across page refreshes - [ ] Theme syncs across browser tabs ### 2. SSR & Hydration - [ ] No hydration warnings in console - [ ] No FOUC on page load - [ ] Server-rendered HTML matches client - [ ] Theme toggle works immediately after mount ### 3. Accessibility - [ ] All text meets contrast requirements - [ ] Focus indicators visible in both themes - [ ] Screen reader compatibility maintained - [ ] Keyboard navigation works in both themes ## Debugging Guide ### 1. Hydration Issues ```bash # Check for hydration warnings npm run dev # Look for: "Warning: Text content did not match" ``` ### 2. CSS Variable Debugging ```css /* Add to debug CSS variables */ .debug-vars::before { content: "text: " var(--color-text) " | bg: " var(--color-bg); position: fixed; top: 0; left: 0; background: red; color: white; z-index: 9999; } ``` ### 3. Theme State Debugging ```jsx // Add to components for debugging const { theme, resolvedTheme, systemTheme } = useTheme() console.log({ theme, resolvedTheme, systemTheme }) ``` ## Performance Best Practices ### 1. CSS Variable Optimization - Use HSL values for easier manipulation - Group related variables together - Minimize number of custom properties - Use inheritance where possible ### 2. Component Optimization - Avoid theme-dependent useEffect hooks - Use CSS-only animations and transitions - Minimize JavaScript theme logic - Prefer CSS variables over conditional classes ## Migration Strategy ### From Inline Classes to CSS Variables 1. **Audit**: Find all `dark:` classes in codebase 2. **Extract**: Create semantic CSS variables 3. **Replace**: Convert components to use CSS classes 4. **Test**: Verify theme switching works 5. **Cleanup**: Remove unused dark: classes ### Example Migration ```jsx // Before

Title

Description

// After

Title

Description

``` ```css .content-container { background-color: var(--color-bg); color: var(--color-text); } .content-title { color: var(--color-text); } .content-description { color: var(--color-text-light); } ``` ## Conclusion Always prioritize CSS variables over inline dark: classes, ensure proper SSR handling, maintain accessibility standards, and test thoroughly across all theme combinations. This approach provides better performance, maintainability, and user experience.