Color Mode Toggle
A UI control letting users switch between light and dark themes, persisted to localStorage and synced with system preference.
Plain English
A well-built color mode toggle has three layers. First, it reads the user's OS preference (prefers-color-scheme) as the default. Second, it lets users override that preference with an explicit toggle. Third, it persists the override to localStorage so the choice survives page reloads. The trickiest part is preventing a flash of the wrong theme on initial load — applying the correct class before React hydrates requires a small synchronous script in the HTML head. The toggle itself should use sun and moon icons with a smooth slide animation.
Technical
Read on load: localStorage.getItem("color-mode") ?? (window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"). Apply class to document.documentElement immediately (synchronous, not in useEffect). Toggle: flip class and save to localStorage. CSS variables change under .dark { --color-bg: #0f172a; --color-text: #f8fafc; }. Animation: transition: background-color 200ms ease, color 200ms ease on the root element.
Live Demo
Color mode toggle
☀️ Light modeBox Shadow
Depth and elevation in UI design
Persist the user's choice to localStorage — nothing is worse than re-applying a preference on every load.
Usage
✓ Good usage
A toggle that shows a sun icon in dark mode and a moon icon in light mode, with a 200ms transition on all colors when switching — the icon clearly indicates the current state.
✗ Bad usage
A toggle labeled "Toggle theme" with no icon, no transition, and that resets to system default on every page load — users must re-apply their preference constantly.
Recommended values
- Theme transition: 200–300ms on background-color and color
- Icon animation: rotate(180deg) + scale on switch
- Default: respect prefers-color-scheme
- Persistence: localStorage key "color-mode"
- Flash prevention: apply class synchronously before React hydration
- Three states: system / light / dark (optional but user-friendly)
Common mistakes
- Flash of wrong theme (FOIT) — reading localStorage in useEffect is too late; the page already rendered with the wrong theme.
- No transition animation — abrupt color switches feel jarring; a 200ms ease transition is smooth and intentional.
- Ignoring system preference — defaulting to light mode for all users ignores those with dark mode set at the OS level.
AI Prompt Example
Copy this into Claude, Cursor, Bolt, or v0.
Implement a color mode toggle with three states: system, light, and dark. Read localStorage on mount and default to prefers-color-scheme. Apply the theme class synchronously in a script tag before React hydrates to prevent flash. Animate the sun/moon icon with a rotation transition. Persist preference to localStorage on toggle.