Skip to content

feat: Add dark mode toggle to application header#34

Merged
platzhersh merged 1 commit intomainfrom
claude/add-dark-mode-Afuee
Jan 31, 2026
Merged

feat: Add dark mode toggle to application header#34
platzhersh merged 1 commit intomainfrom
claude/add-dark-mode-Afuee

Conversation

@platzhersh
Copy link
Owner

@platzhersh platzhersh commented Jan 31, 2026

Summary

This PR adds dark mode functionality to the application with a toggle button in the header. The theme preference is persisted to localStorage and respects the user's system color scheme preference on first visit.

Key Changes

  • Added dark mode toggle button to the header navigation
  • Implemented theme persistence using localStorage
  • Added system preference detection for initial theme selection
  • Imported Moon and Sun icons from lucide-vue-next for the toggle button
  • Integrated Vue composition API (ref, onMounted) for state management

Implementation Details

  • The isDark ref tracks the current theme state
  • toggleDark() function updates the theme, applies the 'dark' class to the document root, and saves the preference to localStorage
  • On component mount, the app checks for a saved theme preference, falls back to system preference if none exists, and applies the appropriate theme
  • The toggle button displays a Sun icon in dark mode and a Moon icon in light mode
  • Styled with consistent hover states and transitions matching the existing design system

https://claude.ai/code/session_01VQYSzcnUUBb8cYZvJZC4eG

Summary by CodeRabbit

  • New Features
    • Added a dark mode toggle button to the application header for seamless switching between light and dark themes.
    • User theme preference is automatically saved and restored on subsequent visits to the application.
    • System-level dark mode preference is detected and applied as the default when no user preference is set.

✏️ Tip: You can customize this high-level summary in your review settings.

Adds a sun/moon toggle button that switches between light and dark themes.
Persists preference to localStorage and respects system preference on first visit.
The dark mode CSS variables were already defined in the project.

https://claude.ai/code/session_01VQYSzcnUUBb8cYZvJZC4eG
@platzhersh platzhersh changed the title Add dark mode toggle to application header feat: Add dark mode toggle to application header Jan 31, 2026
@coderabbitai
Copy link

coderabbitai bot commented Jan 31, 2026

Walkthrough

The change introduces dark mode toggle functionality to the application. It adds Moon and Sun icon imports, creates a reactive isDark state, implements a toggleDark function to manage theme switching with localStorage persistence, and includes onMounted lifecycle logic to initialize theme from saved preferences or system settings. A toggle button is rendered in the header.

Changes

Cohort / File(s) Summary
Dark Mode Feature
web/src/App.vue
Added dark mode toggle with Moon/Sun icons, reactive isDark state, toggleDark function for theme management and localStorage persistence, onMounted lifecycle hook for theme initialization from localStorage or system preference, and toggle button in header with dynamic icon switching.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 By moonlight pale, the toggle glows,
A rabbit's quest for softer tones—
Sun and Moon dance in the code,
While localStorage recalls the load,
Dark or bright, the choice is yours! 🌙✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: Add dark mode toggle to application header' clearly and directly summarizes the main change in the pull request - adding dark mode functionality with a toggle button in the header.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch claude/add-dark-mode-Afuee

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@web/src/App.vue`:
- Around line 8-17: ESLint flags `localStorage` as undefined in toggleDark and
the onMounted block; update references to use a global-qualified accessor (e.g.,
window.localStorage or globalThis.localStorage) to satisfy the linter without
changing behavior. Specifically, inside the toggleDark function (and where
isDark is initialized in the onMounted callback used alongside
document.documentElement.classList.toggle), replace localStorage.getItem/setItem
usages with window.localStorage.getItem/setItem (or globalThis.localStorage) so
the symbols remain unique and lint-clean.
- Around line 46-49: The button element with `@click`="toggleDark" should have its
static and bound attributes ordered before event handlers to satisfy the linter:
move the class and :title attributes to appear before the `@click` attribute on
the <button> (referencing the button that calls the toggleDark method and uses
isDark for the title). Update the attribute order so class and :title come
first, then `@click`, preserving the same values and spacing.

Comment on lines +8 to +17
function toggleDark() {
isDark.value = !isDark.value
document.documentElement.classList.toggle('dark', isDark.value)
localStorage.setItem('theme', isDark.value ? 'dark' : 'light')
}

onMounted(() => {
const saved = localStorage.getItem('theme')
isDark.value = saved === 'dark' || (!saved && window.matchMedia('(prefers-color-scheme: dark)').matches)
document.documentElement.classList.toggle('dark', isDark.value)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix ESLint no-undef for localStorage (CI is failing).

Lint/pipeline errors report localStorage as undefined on Line 11 and Line 15. Use window.localStorage (or globalThis.localStorage) to satisfy the ESLint environment and keep behavior unchanged.

🐛 Proposed fix
 function toggleDark() {
   isDark.value = !isDark.value
   document.documentElement.classList.toggle('dark', isDark.value)
-  localStorage.setItem('theme', isDark.value ? 'dark' : 'light')
+  window.localStorage.setItem('theme', isDark.value ? 'dark' : 'light')
 }

 onMounted(() => {
-  const saved = localStorage.getItem('theme')
+  const saved = window.localStorage.getItem('theme')
   isDark.value = saved === 'dark' || (!saved && window.matchMedia('(prefers-color-scheme: dark)').matches)
   document.documentElement.classList.toggle('dark', isDark.value)
 })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function toggleDark() {
isDark.value = !isDark.value
document.documentElement.classList.toggle('dark', isDark.value)
localStorage.setItem('theme', isDark.value ? 'dark' : 'light')
}
onMounted(() => {
const saved = localStorage.getItem('theme')
isDark.value = saved === 'dark' || (!saved && window.matchMedia('(prefers-color-scheme: dark)').matches)
document.documentElement.classList.toggle('dark', isDark.value)
function toggleDark() {
isDark.value = !isDark.value
document.documentElement.classList.toggle('dark', isDark.value)
window.localStorage.setItem('theme', isDark.value ? 'dark' : 'light')
}
onMounted(() => {
const saved = window.localStorage.getItem('theme')
isDark.value = saved === 'dark' || (!saved && window.matchMedia('(prefers-color-scheme: dark)').matches)
document.documentElement.classList.toggle('dark', isDark.value)
})
🧰 Tools
🪛 GitHub Actions: Frontend CI

[error] 11-11: ESLint: 'localStorage' is not defined. (no-undef)

🪛 GitHub Check: lint-and-test

[failure] 15-15:
'localStorage' is not defined


[failure] 11-11:
'localStorage' is not defined

🤖 Prompt for AI Agents
In `@web/src/App.vue` around lines 8 - 17, ESLint flags `localStorage` as
undefined in toggleDark and the onMounted block; update references to use a
global-qualified accessor (e.g., window.localStorage or globalThis.localStorage)
to satisfy the linter without changing behavior. Specifically, inside the
toggleDark function (and where isDark is initialized in the onMounted callback
used alongside document.documentElement.classList.toggle), replace
localStorage.getItem/setItem usages with window.localStorage.getItem/setItem (or
globalThis.localStorage) so the symbols remain unique and lint-clean.

Comment on lines +46 to +49
<button
@click="toggleDark"
class="inline-flex items-center justify-center rounded-md p-2 text-muted-foreground transition-colors hover:text-primary hover:bg-accent"
:title="isDark ? 'Switch to light mode' : 'Switch to dark mode'"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Reorder button attributes to clear lint warnings.

Static analysis reports attribute order warnings. Move class and :title before @click to satisfy the lint rule.

🧹 Proposed fix
-          <button
-            `@click`="toggleDark"
-            class="inline-flex items-center justify-center rounded-md p-2 text-muted-foreground transition-colors hover:text-primary hover:bg-accent"
-            :title="isDark ? 'Switch to light mode' : 'Switch to dark mode'"
-          >
+          <button
+            class="inline-flex items-center justify-center rounded-md p-2 text-muted-foreground transition-colors hover:text-primary hover:bg-accent"
+            :title="isDark ? 'Switch to light mode' : 'Switch to dark mode'"
+            `@click`="toggleDark"
+          >
🧰 Tools
🪛 GitHub Check: lint-and-test

[warning] 49-49:
Attribute ":title" should go before "@click"


[warning] 48-48:
Attribute "class" should go before "@click"

🤖 Prompt for AI Agents
In `@web/src/App.vue` around lines 46 - 49, The button element with
`@click`="toggleDark" should have its static and bound attributes ordered before
event handlers to satisfy the linter: move the class and :title attributes to
appear before the `@click` attribute on the <button> (referencing the button that
calls the toggleDark method and uses isDark for the title). Update the attribute
order so class and :title come first, then `@click`, preserving the same values
and spacing.

@platzhersh platzhersh merged commit 316445b into main Jan 31, 2026
1 of 2 checks passed
@platzhersh platzhersh deleted the claude/add-dark-mode-Afuee branch January 31, 2026 21:49
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.

2 participants

Comments