- 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
350 lines
8.2 KiB
Markdown
350 lines
8.2 KiB
Markdown
# 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
|
|
<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
|
|
```jsx
|
|
// 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
|
|
```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 (
|
|
<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
|
|
```jsx
|
|
// ❌ 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
|
|
```jsx
|
|
// ✅ 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:
|
|
|
|
```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
|
|
<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>
|
|
```
|
|
|
|
```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.
|