Skip to content

feat: Add dark/light theme toggle with localStorage persistence#7

Merged
NovaCode37 merged 2 commits intoNovaCode37:mainfrom
dotsystemsdevs:main
Apr 8, 2026
Merged

feat: Add dark/light theme toggle with localStorage persistence#7
NovaCode37 merged 2 commits intoNovaCode37:mainfrom
dotsystemsdevs:main

Conversation

@dotsystemsdevs
Copy link
Copy Markdown

Summary

Implements a complete theme switcher with persistent storage, allowing users to toggle between dark and light themes. The preference is saved to localStorage and restored on page load.

Changes

Files Modified

  1. frontend/src/app/globals.css

    • Added light theme CSS variables: --background, --surface-1, --surface-2, --border-1, --text-1, etc.
    • Added dark theme variables for consistency
    • Updated body styles to use CSS variables
  2. frontend/src/components/Topbar.tsx

    • Imported useTheme hook and Sun/Moon icons
    • Added theme toggle button next to GitHub link
    • Shows appropriate icon based on current theme
    • Accessible with ARIA labels and title attributes
  3. frontend/src/lib/useTheme.ts (New)

    • Created custom hook for theme management
    • Handles localStorage persistence
    • Falls back to system color scheme preference
    • Sets data-theme attribute on document root

Features

✅ Toggle between dark and light themes
✅ Persistent theme preference using localStorage
✅ Respects system color scheme preference
✅ Smooth theme transitions
✅ Accessible UI with proper labels
✅ Sun/Moon icons for visual feedback

Testing

  • Theme toggle button appears in Topbar
  • Clicking button switches between dark/light themes
  • Refreshing page preserves theme preference
  • System preference is used if no saved preference exists

Closes #6

Implement theme switcher functionality:
- Add useTheme hook with localStorage persistence
- Add light theme CSS variables alongside dark theme
- Add theme toggle button in Topbar with Sun/Moon icons
- Respects system color scheme preference as fallback
- Smooth transitions between themes

Files modified:
- frontend/src/app/globals.css: Added theme variables
- frontend/src/components/Topbar.tsx: Added toggle button
- frontend/src/lib/useTheme.ts: New theme hook (created)

The theme preference is stored in localStorage and restored on page load.
Falls back to system preference if no saved preference exists.

Closes NovaCode37#6
@NovaCode37
Copy link
Copy Markdown
Owner

NovaCode37 commented Apr 8, 2026

Hey, thanks for the PR! Great work on the overall structure - localStorage + system preference fallback is the right approach.

However, there are a few issues that need to be fixed before I can merge this:

1. FOUC

The theme is applied in useEffect, which runs after React hydration. Users who selected "light" will see a dark→light flash on every page load. Fix: add a blocking inline script in layout.tsx inside using dangerouslySetInnerHTML that reads localStorage('theme') or matchMedia('prefers-color-scheme: light') and sets document.documentElement.setAttribute('data-theme', t) before React renders. This ensures data-theme is set before any CSS is evaluated.

2. Duplicate import in Topbar.tsx - will break the build

The diff shows two import lines from lucide-react. Remove the old one without Sun/Moon and keep only the new one that includes Sun and Moon.

3. Redundant hardcoded colors in globals.css

The body rule still has hardcoded background: #0d1117 and color: #e6edf3 before the CSS variable versions. Remove both hardcoded lines, keep only background: var(--background) and color: var(--text-1).

4. No CSS transition between themes

The PR description mentions "Smooth transitions between themes" but there's no transition in the CSS. Add transition: background-color 0.2s ease, color 0.2s ease to the body rule.

5. Mounted is unused

The useTheme hook returns mounted but it's never used in Topbar.tsx. Either use it to conditionally render the toggle button (prevents icon mismatch before hydration) or remove it from the hook.

Once these are addressed, I'll be happy to merge. Let me know if you have any questions!

1. FOUC: Add blocking inline script in layout.tsx that reads
   localStorage/matchMedia and sets data-theme before React hydrates
2. CSS transition: Add transition: background-color 0.2s ease, color 0.2s ease
   to body rule in globals.css
3. mounted: Use mounted flag in Topbar to guard icon render and prevent
   Sun/Moon mismatch before hydration (falls back to Sun)
@NovaCode37 NovaCode37 merged commit 87783b8 into NovaCode37:main Apr 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] Dark/light theme toggle

2 participants