- Fix critical navigation dark theme issues (white background on scroll, poor text contrast) - Replace all hardcoded Tailwind colors with semantic CSS classes throughout Footer and Navigation - Add complete set of CSS variable classes for consistent theming (.nav-bar, .footer, .nav-link, etc.) - Eliminate hardcoded colors from home page and add missing CTA button classes - Ensure proper light/dark theme support with CSS-only switching for optimal performance - Update Hero component to use clean backgrounds instead of gradients - Set light theme as default for better user experience - Add comprehensive theme implementation documentation - Update memory bank with current implementation status Files modified: - globals.css: Added Navigation and Footer CSS classes using CSS variables - Navigation.tsx: Fixed dark theme background and text contrast issues - Footer.tsx: Complete conversion from hardcoded colors to semantic classes - page.tsx: Fixed CTA section and removed hardcoded colors - Hero.tsx: Clean background implementation - layout.tsx: Set light theme as default - activeContext.md: Updated current work status - themeImplementation.md: Added comprehensive theme methodology guide
8.2 KiB
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
: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:
.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:
// ✅ CORRECT - Uses semantic classes
<div className="card">
<h3 className="card-title">Title</h3>
<p className="card-description">Description</p>
</div>
// ❌ AVOID - Inline dark: classes
<div className="bg-white dark:bg-gray-800 text-gray-900 dark:text-white">
<h3 className="text-gray-900 dark:text-white">Title</h3>
<p className="text-gray-600 dark:text-gray-300">Description</p>
</div>
next-themes Implementation Checklist
1. Essential Setup
// app/layout.tsx
import { ThemeProvider } from 'next-themes'
export default function Layout({ children }) {
return (
<html suppressHydrationWarning>
<body>
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem={true}
storageKey="theme"
themes={['light', 'dark', 'system']}
>
{children}
</ThemeProvider>
</body>
</html>
)
}
2. Theme Toggle Component
'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 (
<select value={theme} onChange={(e) => setTheme(e.target.value)}>
<option value="system">System</option>
<option value="dark">Dark</option>
<option value="light">Light</option>
</select>
)
}
Critical Implementation Rules
1. NEVER Access Theme During SSR
// ❌ WRONG - Causes hydration mismatch
function Component() {
const { theme } = useTheme()
return (
<div className={theme === 'dark' ? 'dark-styles' : 'light-styles'}>
Content
</div>
)
}
// ✅ CORRECT - Use CSS variables only
function Component() {
return (
<div className="themed-container">
Content
</div>
)
}
2. Always Check Mount State
// ✅ CORRECT Pattern
function ThemeAwareComponent() {
const [mounted, setMounted] = useState(false)
const { theme } = useTheme()
useEffect(() => setMounted(true), [])
if (!mounted) {
return <div className="skeleton-loader" /> // Fallback UI
}
// Safe to use theme here
return <div>Theme: {theme}</div>
}
3. CSS Variable Hierarchy
Structure variables from general to specific:
: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):
: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
*: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
@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
# Check for hydration warnings
npm run dev
# Look for: "Warning: Text content did not match"
2. CSS Variable Debugging
/* 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
// 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
- Audit: Find all
dark:classes in codebase - Extract: Create semantic CSS variables
- Replace: Convert components to use CSS classes
- Test: Verify theme switching works
- Cleanup: Remove unused dark: classes
Example Migration
// Before
<div className="bg-white dark:bg-gray-900 text-gray-900 dark:text-white">
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">Title</h1>
<p className="text-gray-600 dark:text-gray-300">Description</p>
</div>
// After
<div className="content-container">
<h1 className="content-title text-2xl font-bold">Title</h1>
<p className="content-description">Description</p>
</div>
.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.