Skip to content

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

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

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions