Appearance
Theme Switching Implementation Plan
For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
Goal: Add dark/light theme switching with system preference detection, header toggle button, and full control in cabinet settings.
Architecture: Nuxt UI v3 includes @nuxtjs/color-mode and provides ready-made components (ColorModeButton, ColorModeSelect). We configure colorMode in nuxt.config.ts, add the built-in ColorModeButton to both layouts' headers, and add ColorModeSelect to the cabinet settings page. No custom components needed — Nuxt UI handles everything including Russian locale translations.
Tech Stack: Nuxt UI v3 (ColorModeButton, ColorModeSelect, useColorMode), @nuxtjs/color-mode, Tailwind CSS 4 dark mode
Task 1: Configure colorMode in nuxt.config.ts
Files:
- Modify:
nuxt.config.ts:62-72(addcolorModesection beforeruntimeConfig)
Step 1: Add colorMode configuration
Add colorMode section to nuxt.config.ts after the image section and before routeRules:
ts
colorMode: {
preference: 'system',
fallback: 'light',
},This tells @nuxtjs/color-mode (bundled with Nuxt UI) to default to system preference, falling back to light if system detection is unavailable.
Step 2: Verify dev server starts
Run: npm run dev Expected: Dev server starts without errors. The <html> element should now get a class="light" or class="dark" based on your OS setting.
Step 3: Commit
bash
git add nuxt.config.ts
git commit -m "feat: configure colorMode with system preference default"Task 2: Add color palette comments to app.config.ts
Files:
- Modify:
app/app.config.ts
Step 1: Add comments with available palette options
Replace the entire file content with:
ts
export default defineAppConfig({
ui: {
colors: {
// Available palettes: red, orange, amber, yellow, lime, green, emerald,
// teal, cyan, sky, blue, indigo, violet, purple, fuchsia, pink, rose
primary: 'blue',
neutral: 'slate',
},
},
})Step 2: Verify no errors
Run: npm run dev Expected: Dev server runs without errors, UI unchanged.
Step 3: Commit
bash
git add app/app.config.ts
git commit -m "docs: add available color palette options to app.config"Task 3: Add ColorModeButton to default layout header
Files:
- Modify:
app/layouts/default.vue:23-33(desktop nav) andapp/layouts/default.vue:36-51(mobile nav area)
Step 1: Add ColorModeButton to desktop nav
In the desktop <nav> section (line 23), add <ColorModeButton /> as the last element before the closing </nav>:
html
<!-- Desktop nav -->
<nav class="hidden md:flex items-center gap-4">
<UButton :label="t('common.addListing')" to="/cabinet/products/new" variant="solid" />
<template v-if="authStore.isAuthenticated">
<UButton :label="t('common.favorites')" to="/cabinet/favorites" variant="ghost" icon="i-lucide-heart" />
<UButton variant="ghost" to="/cabinet" icon="i-lucide-user">
{{ authStore.user?.display_name }}
</UButton>
<UButton :label="t('common.logout')" variant="ghost" icon="i-lucide-log-out" @click="logout" />
</template>
<UButton v-else :label="t('common.login')" to="/auth/login" variant="ghost" icon="i-lucide-log-in" />
<ColorModeButton />
</nav>Step 2: Add ColorModeButton to mobile nav area
In the mobile nav area (the <div class="flex md:hidden items-center gap-2">), add <ColorModeButton size="sm" /> before the hamburger menu button:
html
<!-- Mobile nav -->
<div class="flex md:hidden items-center gap-2">
<UButton
to="/cabinet/products/new"
variant="solid"
size="sm"
icon="i-lucide-plus"
:aria-label="t('common.addListing')"
/>
<ColorModeButton size="sm" />
<UButton
variant="ghost"
size="sm"
icon="i-lucide-menu"
:aria-label="t('common.menu')"
@click="mobileMenuOpen = true"
/>
</div>Step 3: Verify visually
Run: npm run dev, open http://localhost:3000 Expected:
- Sun/moon icon visible in desktop header (right side, after auth buttons)
- Sun/moon icon visible in mobile header (between "+" and hamburger)
- Clicking the icon toggles between light and dark mode
- Page colors change: background, text, borders all respect dark mode
- Preference persists after page reload
Step 4: Commit
bash
git add app/layouts/default.vue
git commit -m "feat: add theme toggle button to default layout header"Task 4: Add ColorModeButton to cabinet layout header
Files:
- Modify:
app/layouts/cabinet.vue:8-18(header area)
Step 1: Add ColorModeButton to header
In the header <div> (the flex container on line 8), add <ColorModeButton /> between the logo and the hamburger button. Wrap the right-side elements in a flex container:
html
<header class="border-b border-gray-200 dark:border-gray-800">
<div class="container mx-auto px-4 py-3 flex items-center justify-between">
<NuxtLink to="/">
<SharedAppLogo />
</NuxtLink>
<div class="flex items-center gap-2">
<ColorModeButton />
<UButton
icon="i-heroicons-bars-3"
variant="ghost"
class="md:hidden"
@click="mobileMenuOpen = true"
/>
</div>
</div>
</header>Step 2: Verify visually
Run: npm run dev, navigate to /cabinet Expected: Sun/moon icon visible in cabinet header, toggles theme correctly.
Step 3: Commit
bash
git add app/layouts/cabinet.vue
git commit -m "feat: add theme toggle button to cabinet layout header"Task 5: Add theme section to cabinet settings page
Files:
- Modify:
app/pages/cabinet/settings.vue - Reference:
i18n/locales/ru.json(for new i18n keys)
Step 1: Add i18n keys for settings page
Add a settings section to i18n/locales/ru.json:
json
"settings": {
"appearance": "Оформление",
"theme": "Тема",
"themeDescription": "Следует настройкам вашего устройства"
}Step 2: Add ColorModeSelect to settings page
Replace the content of app/pages/cabinet/settings.vue:
html
<script setup lang="ts">
definePageMeta({ layout: 'cabinet', middleware: 'auth' })
const { t } = useI18n()
</script>
<template>
<div class="space-y-8">
<h1 class="text-2xl font-bold">{{ t('common.settings') }}</h1>
<section>
<h2 class="text-lg font-semibold mb-4">{{ t('settings.appearance') }}</h2>
<div class="flex items-center gap-4">
<label class="text-sm font-medium">{{ t('settings.theme') }}</label>
<ColorModeSelect />
</div>
</section>
</div>
</template>Step 3: Verify visually
Run: npm run dev, navigate to /cabinet/settings Expected:
- "Оформление" section heading visible
- Dropdown with three options: "Системная", "Светлая", "Тёмная"
- Selecting an option immediately switches the theme
- The header toggle button reflects the current mode
Step 4: Commit
bash
git add i18n/locales/ru.json app/pages/cabinet/settings.vue
git commit -m "feat: add theme selection to cabinet settings page"Task 6: Visual audit of existing pages in dark mode
Files:
- Potentially modify: any file with hardcoded colors missing
dark:variant
Step 1: Check all pages in dark mode
With dev server running, switch to dark mode via the header toggle. Visit each page and check for visual issues:
/— homepage/catalog— catalog with filters/product/{id}— product detail (use any valid ID)/auth/login— login form/auth/register— registration form/auth/forgot-password— forgot password/cabinet— my listings/cabinet/products/new— new listing form/cabinet/settings— settings (just added)
Step 2: Fix any issues found
Look for:
bg-whitewithoutdark:bg-gray-900(ordark:bg-[var(--ui-bg)])text-gray-*withoutdark:text-gray-*variantborder-gray-*withoutdark:border-gray-*variant- Any hardcoded colors that look wrong in dark mode
Fix each issue in the relevant file. Nuxt UI components (UButton, UInput, UCard, etc.) should look correct automatically.
Step 3: Commit fixes (if any)
bash
git add -A
git commit -m "fix: resolve dark mode visual issues across pages"Task 7: Update CLAUDE.md progress
Files:
- Modify:
CLAUDE.md(Progress section)
Step 1: Mark theme switching as complete
In the Progress section of CLAUDE.md, change:
markdown
- [ ] Theme switching (dark/light mode)to:
markdown
- [x] Theme switching (dark/light mode)Step 2: Commit
bash
git add CLAUDE.md
git commit -m "docs: mark theme switching as complete"Task 8: Write test for ThemeToggle behavior
Files:
- Create:
app/shared/ui/ThemeToggle.test.ts
Step 1: Write the test
Since we're using Nuxt UI's built-in ColorModeButton, the test verifies it renders and integrates correctly:
ts
import { describe, it, expect } from 'vitest'
import { mountSuspended } from '@nuxt/test-utils/runtime'
import { ColorModeButton } from '#components'
describe('ColorModeButton', () => {
it('renders without errors', async () => {
const wrapper = await mountSuspended(ColorModeButton)
expect(wrapper.find('button').exists()).toBe(true)
})
it('has accessible aria-label', async () => {
const wrapper = await mountSuspended(ColorModeButton)
const button = wrapper.find('button')
const label = button.attributes('aria-label')
expect(label).toBeTruthy()
})
})Step 2: Run the test to verify it passes
Run: npm run test:run -- app/shared/ui/ThemeToggle.test.ts Expected: 2 tests pass.
Step 3: Commit
bash
git add app/shared/ui/ThemeToggle.test.ts
git commit -m "test: add ColorModeButton integration test"Task 9: Final verification
Step 1: Run linter
Run: npm run lint Expected: No errors.
Step 2: Run all tests
Run: npm run test:run Expected: All tests pass.
Step 3: Run typecheck
Run: npm run typecheck Expected: No type errors.
Step 4: Verify dark mode end-to-end
- Open
http://localhost:3000 - Click theme toggle → dark mode activates
- Refresh page → dark mode persists
- Navigate to
/cabinet/settings→ select "Системная" → follows OS - Select "Светлая" → forces light mode
- Click header toggle → switches to dark
Step 5: Commit any remaining fixes and push
bash
git push