Skip to content

[Feature] Implement Dark/Light Mode Theme Toggle with System Preference Detection #3

@Franpastoragusti

Description

@Franpastoragusti

Problem Statement

The application currently has a hardcoded dark theme (see app/layout.tsx:36), preventing users from choosing their preferred color scheme. This creates accessibility issues for users who:

  • Prefer light mode for better readability in bright environments
  • Have visual sensitivities to dark backgrounds
  • Want consistency with their OS-level theme preference

Current limitation: The className="dark" is hardcoded in the root layout, ignoring user preferences and system settings.

User Value

Users will gain:

  1. Theme Control: Toggle between light and dark modes with a single click via a navbar button
  2. Smart Defaults: Initial theme automatically respects OS-level preference (Windows/macOS dark mode setting)
  3. Persistence: Theme choice persists across browser sessions via localStorage
  4. Smooth Experience: Subtle 150ms fade transitions during theme switches
  5. Accessibility: WCAG 2.1 AA compliant toggle with keyboard navigation and proper ARIA labels

Concrete Example: A user working in a bright office can switch to light mode for reduced eye strain, and when they return later, the app remembers their preference. If a new user visits with dark mode enabled in their OS, the app starts in dark mode automatically.

Technical Implementation

Architecture Decision: Theme management is an infrastructure-level concern (not a feature), implemented as:

  1. ThemeProvider (components/theme-provider.tsx) - Thin wrapper around next-themes
  2. ThemeToggle (components/theme-toggle.tsx) - Two-state toggle button (Light ↔ Dark)
  3. Root Layout (app/layout.tsx) - Wrap with ThemeProvider, remove hardcoded dark class
  4. Navbar (components/navbar.tsx) - Integrate ThemeToggle component (right-aligned)

Dependencies: All required packages are already installed:

  • next-themes@^0.2.1
  • lucide-react@^0.460.0
  • button (shadcn/ui) ✅

Key Design Choices:

  • Two-state toggle (Light ↔ Dark only) for maximum simplicity
  • System preference as default (defaultTheme="system")
  • Responsive button sizing: h-8 w-8 md:h-10 md:w-10
  • Sun/Moon icons with smooth rotate/scale animations
  • No dropdown menu needed (simpler UX)

Definition of Done

Implementation:

  • Create components/theme-provider.tsx with next-themes wrapper
  • Create components/theme-toggle.tsx with two-state toggle button
  • Update app/layout.tsx: Add ThemeProvider, remove hardcoded "dark" class, add suppressHydrationWarning
  • Update components/navbar.tsx: Integrate ThemeToggle component (right-aligned)
  • Add CSS transitions (150ms fade for smooth theme switching)
  • Both components have ABOUTME comments

Testing:

  • Unit tests for ThemeToggle component (5 core scenarios):
    1. Component renders without crashing
    2. Toggle functionality works (light ↔ dark)
    3. localStorage persistence
    4. Initial load respects saved preference
    5. CSS variables update correctly
  • Test coverage >80% for theme components
  • Manual testing checklist completed (see below)
  • Zero console errors/warnings
  • No regressions in conversation feature

Documentation:

  • Update CLAUDE.md with Theme Management section
  • Update .claude/sessions/context_session_dark_light_mode.md with completion status

Quality:

  • Code matches existing style and conventions
  • Follows hexagonal architecture principles (infrastructure concern)
  • CI/CD passes (lint, build, tests)
  • Code review approved

Manual Testing Checklist

Basic Flow:

  1. Start dev server (yarn dev)
  2. Open app in browser - verify initial theme matches OS preference
  3. Click theme toggle button - verify smooth transition to opposite theme
  4. Click again - verify return to original theme
  5. Check DevTools → Application → Local Storage - verify theme key exists with correct value
  6. Refresh page - verify theme persists

Edge Case Testing:

  1. Rapid Toggling: Click toggle 10 times quickly - verify no UI glitches or console errors
  2. New User (No localStorage): Clear localStorage → refresh → verify defaults to system preference
  3. System Preference Override: Set OS to dark mode → app starts dark → toggle to light → app stays light (ignores OS)
  4. Private Browsing: Open in incognito mode → verify theme still works (falls back to in-memory storage)

Error Handling:

  1. Hydration Check: Open console → verify NO hydration mismatch errors
  2. White Flash Test: Toggle theme multiple times → verify NO white flash during transitions
  3. Build Test: Run yarn build → verify NO build errors related to theme

Integration Testing:

  1. Conversation Feature: Send chat messages in both themes → verify streaming works, no regressions
  2. Navbar Layout: Verify theme toggle is right-aligned, proper spacing
  3. Responsive Design: Test on mobile (DevTools responsive mode) → verify button sizing (h-8 w-8h-10 w-10 on md+)

Accessibility Testing:

  1. Keyboard Navigation: Tab to theme toggle → press Enter/Space → verify toggle works
  2. Screen Reader: Use VoiceOver/NVDA → verify button announces current theme state
  3. ARIA Labels: Inspect button element → verify aria-label updates dynamically ("Switch to dark mode" / "Switch to light mode")
  4. Focus Visible: Tab to button → verify focus ring is visible

Visual Testing:

  1. Icon Animation: Toggle theme → verify Sun/Moon icons rotate and scale smoothly
  2. Color Consistency: Check all UI elements (navbar, chat bubbles, buttons) → verify colors update correctly
  3. CSS Variables: Inspect elements → verify --background, --foreground, etc. update on toggle

Related Documentation

Comprehensive planning documentation available:

  • .claude/sessions/context_session_dark_light_mode.md - Complete implementation plan with all decisions
  • .claude/doc/dark_light_mode/theme-architecture-integration.md - Architecture analysis
  • .claude/doc/dark_light_mode/theme-testing-strategy.md - Testing strategy
  • .claude/doc/dark_light_mode/theme-toggle-component-design.md - UI/UX design details

Estimated Effort

Total: 2-2.5 hours

Breakdown:

  • Verify dependencies: 5 min
  • Create ThemeProvider: 10 min
  • Create ThemeToggle: 20 min
  • Update Layout: 15 min
  • Update Navbar: 10 min
  • Manual Testing: 15 min
  • Write Core Tests: 30-45 min
  • Documentation: 10 min

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions