lad-website/cline_docs/themeImplementation.md
TheMaddax e8ba34e3b7 feat: Implement comprehensive CSS variable theme system
- 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
2025-06-02 10:27:34 -06:00

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

  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

// 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.