3.9 KiB
Dark Mode Implementation Plan
Context
The Synculous client already has a bare @media (prefers-color-scheme: dark) in globals.css that only flips body background/foreground. Every component uses hardcoded Tailwind classes (bg-white, bg-gray-50, text-gray-900, etc.) with no dark: variants, so manually switching modes via a button has no effect. The goal is a user-controlled toggle that persists preference, respects the OS default, and applies comprehensive dark styles across all pages.
Steps
1. Configure Tailwind v4 for class-based dark mode
File: src/app/globals.css
Add the Tailwind v4 CSS directive that maps dark: utilities to an ancestor .dark class:
@variant dark (&:where(.dark, .dark *));
Replace the @media (prefers-color-scheme: dark) block with a .dark selector:
.dark {
--background: #0a0a0a;
--foreground: #ededed;
}
2. Create ThemeProvider
New file: src/components/theme/ThemeProvider.tsx
A minimal React context provider:
- State:
isDark: boolean - On mount: reads
localStorage.getItem('theme'), falls back towindow.matchMedia('(prefers-color-scheme: dark)').matches - Applies/removes
darkclass ondocument.documentElement toggleDark()function that flips state and saves tolocalStorage- Exports
useTheme()hook
3. Anti-flash script + wrap in layout
File: src/app/layout.tsx
- Add ThemeProvider wrapping
AuthProvider - Add inline
<script>before<body>content to set.darkclass before hydration (prevents white flash)
4. Dark mode toggle button in dashboard header
File: src/app/dashboard/layout.tsx
- Import
useThemefrom the ThemeProvider - Add a sun/moon icon button in the existing right-side icon row (between Settings and Logout)
SunIconandMoonIconalready exist insrc/components/ui/Icons.tsx— no new icons needed
5. Add dark: variants to all components
Color mapping:
| Light class | Dark variant |
|---|---|
bg-white |
dark:bg-gray-800 |
bg-gray-50 |
dark:bg-gray-900 |
bg-gray-100 |
dark:bg-gray-700 |
bg-gray-200 |
dark:bg-gray-600 |
text-gray-900 |
dark:text-gray-100 |
text-gray-700 |
dark:text-gray-300 |
text-gray-600 |
dark:text-gray-400 |
text-gray-500 |
dark:text-gray-400 |
border-gray-200 |
dark:border-gray-700 |
border-gray-100 |
dark:border-gray-800 |
border-gray-300 |
dark:border-gray-600 |
bg-indigo-50 |
dark:bg-indigo-900/30 |
bg-green-50 |
dark:bg-green-900/30 |
bg-amber-50 |
dark:bg-amber-900/30 |
bg-blue-50 |
dark:bg-blue-900/30 |
bg-red-50 |
dark:bg-red-900/30 |
Files to update (in order):
src/app/dashboard/layout.tsx— shell, header, navsrc/app/dashboard/page.tsx— Today viewsrc/app/dashboard/routines/page.tsxsrc/app/dashboard/routines/new/page.tsxsrc/app/dashboard/routines/[id]/page.tsxsrc/app/dashboard/routines/[id]/launch/page.tsxsrc/app/dashboard/routines/[id]/run/page.tsxsrc/app/dashboard/templates/page.tsxsrc/app/dashboard/history/page.tsxsrc/app/dashboard/stats/page.tsxsrc/app/dashboard/medications/page.tsxsrc/app/dashboard/medications/new/page.tsxsrc/app/dashboard/settings/page.tsxsrc/app/login/page.tsxsrc/components/session/VisualTimeline.tsxsrc/components/notifications/PushNotificationToggle.tsx
Files to create
src/components/theme/ThemeProvider.tsx(new)
Files to modify
src/app/globals.csssrc/app/layout.tsx- All pages listed in step 5
Verification
- Run
npm run devinsynculous-client/ - Navigate to
/dashboard— should default to OS preference - Click the sun/moon toggle in the header — UI should switch immediately
- Refresh the page — theme should persist (no flash)
- Switch OS theme — manual override takes priority; no override follows OS