6.4 KiB
Implementation Plan: System-Theme-Erkennung (Light/Dark Mode)
Overview
Integrate next-themes into the existing React + Vite + Tailwind + shadcn/ui app to enable automatic OS theme detection, manual theme switching (light/dark/system), and persistent user preference. The CSS variables in src/index.css will be restructured so :root defines the light theme and .dark defines the dark theme. A ThemeToggle component will be added to the Header, and an inline script in index.html will prevent FOUC.
Tasks
-
1. Restructure CSS variables and set up testing dependencies
-
1.1 Restructure CSS custom properties in
src/index.css- Move the current
:rootdark color values into the.darkblock - Define new light theme values in
:root(white backgrounds, dark text, matching design spec) - Ensure all design tokens are covered: background, foreground, card, primary, secondary, muted, accent, border, input, ring, destructive, popover, sidebar, chart-1 through chart-5
- Update hardcoded color values in component styles (
.glass-nav,.card-minimal,.project-card,.btn,.text-gradient, etc.) to use CSS variables where possible - Add dark-mode-aware variants for component styles that use hardcoded HSL values
- Requirements: 4.1, 4.2, 4.3, 4.4
- Move the current
-
1.2 Install
fast-checkas a dev dependency- Run
npm install --save-dev fast-check - Requirements: (testing infrastructure)
- Run
-
-
2. Create ThemeProvider wrapper and integrate into App
-
2.1 Create
src/components/ThemeProvider.tsx- Export a thin wrapper around
ThemeProviderfromnext-themes - Configure with
attribute="class",defaultTheme="system",enableSystem={true},storageKey="theme" - Accept
childrenand optional override props - Requirements: 1.1, 1.2, 1.3, 3.1, 3.2
- Export a thin wrapper around
-
2.2 Wrap the App component tree with ThemeProvider in
src/App.tsx- Import ThemeProvider and wrap it as the outermost provider around the existing component tree
- Requirements: 1.1, 2.3, 2.4, 2.5
-
* 2.3 Write property test: System-Theme-Auflösung (Property 1)
- Property 1: System-Theme-Auflösung
- Generate arbitrary system theme values (light/dark), set preference to "system", verify
resolvedTheme === systemTheme - Use
fast-checkwith minimum 100 iterations - Validates: Requirements 1.1, 1.2, 1.3, 2.5
-
* 2.4 Write property test: Explizite Theme-Wahl überschreibt System (Property 2)
- Property 2: Explizite Theme-Wahl überschreibt System
- Generate arbitrary combinations of system theme and explicit choice ("light"/"dark"), verify
resolvedTheme === expliziteWahl - Use
fast-checkwith minimum 100 iterations - Validates: Requirements 2.3, 2.4
-
* 2.5 Write property test: Persistenz-Round-Trip (Property 3)
- Property 3: Persistenz-Round-Trip
- Generate arbitrary valid theme values ("light", "dark", "system"), set via
setTheme(), read fromlocalStorage, verify equality and correct restoration on simulated reload - Use
fast-checkwith minimum 100 iterations - Validates: Requirements 3.1, 3.2
-
-
3. Checkpoint
- Ensure all tests pass, ask the user if questions arise.
-
4. Create ThemeToggle component
-
4.1 Create
src/components/ThemeToggle.tsx- Build a button component using shadcn/ui
DropdownMenuandButton - Use
useTheme()hook fromnext-themesfortheme,setTheme,resolvedTheme - Display Sun icon (lucide-react) for light, Moon for dark, Monitor for system
- Implement mounted-state check to prevent hydration mismatch on icon rendering
- Dropdown offers three options: "Hell" (light), "Dunkel" (dark), "System" (system)
- Add
aria-labeldescribing current state (e.g., "Theme wechseln, aktuell: Dunkel") - Ensure keyboard navigability (Tab, Enter/Space)
- Accept optional
classNameprop for positioning flexibility - Requirements: 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 6.1, 6.2, 6.3
- Build a button component using shadcn/ui
-
* 4.2 Write unit tests for ThemeToggle
- Test that the toggle button renders in the DOM
- Test that dropdown shows three options (Hell, Dunkel, System)
- Test correct icon rendering per theme state
- Test
aria-labelcontains current theme state - Test keyboard interaction (focus, Enter/Space opens dropdown)
- Requirements: 2.1, 2.2, 2.6, 6.1, 6.2, 6.3
-
-
5. Integrate ThemeToggle into Header
-
5.1 Add ThemeToggle to desktop navigation in
src/components/Header.tsx- Import ThemeToggle and place it in the
NavBodyactions area, before the "Kontakt" button - Requirements: 2.1
- Import ThemeToggle and place it in the
-
5.2 Add ThemeToggle to mobile navigation in
src/components/Header.tsx- Place ThemeToggle in the
MobileNavHeader, positioned next to the hamburger toggle icon - Requirements: 2.1
- Place ThemeToggle in the
-
* 5.3 Write unit tests for Header ThemeToggle integration
- Test that ThemeToggle is present in the rendered Header component
- Requirements: 2.1
-
-
6. Add FOUC-prevention inline script to
index.html-
6.1 Add inline
<script>block in the<head>ofindex.html- Read
localStorage.getItem("theme") - If value is "dark", add
class="dark"to<html> - If value is "system" or absent, check
window.matchMedia("(prefers-color-scheme: dark)")and set class accordingly - If value is "light", ensure no
darkclass is present - Script must execute synchronously before any rendering
- Requirements: 5.1, 5.2, 3.2
- Read
-
* 6.2 Write unit test for FOUC-prevention logic
- Extract the inline script logic into a testable function
- Test that correct class is applied for each localStorage value and system preference combination
- Requirements: 5.1
-
-
7. Final checkpoint
- Ensure all tests pass, ask the user if questions arise.
Notes
- Tasks marked with
*are optional and can be skipped for faster MVP - Each task references specific requirements for traceability
- Checkpoints ensure incremental validation
- Property tests validate universal correctness properties from the design document
- Unit tests validate specific examples and edge cases
- The project uses TypeScript throughout — all new files should be
.tsx/.ts next-themesis already installed (^0.3.0),fast-checkneeds to be added as a dev dependencylucide-reactis already available for Sun/Moon/Monitor icons- shadcn/ui
DropdownMenuandButtoncomponents already exist insrc/components/ui/