| name | dark-mode-and-theming |
| description | Use when adding dark mode, theme toggle, system theme support, or fixing hardcoded colors that break in dark mode. Not for design-token migration unrelated to dark mode. |
Dark mode and theming
Use CSS variables + Tailwind's dark: variant. Never hardcode #fff / #000 in components.
Setup (shadcn / Tailwind)
tailwind.config.ts → darkMode: ["class"]
index.css defines tokens for :root and .dark:
:root {
--background: 0 0% 100%;
--foreground: 222 47% 11%;
--primary: 221 83% 53%;
--primary-foreground: 0 0% 100%;
--border: 214 32% 91%;
}
.dark {
--background: 222 47% 11%;
--foreground: 210 40% 98%;
--primary: 217 91% 60%;
--primary-foreground: 222 47% 11%;
--border: 217 33% 18%;
}
- Components use semantic classes:
bg-background, text-foreground, border-border — never bg-white.
Theme provider
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
<App />
</ThemeProvider>
Persist user choice (localStorage) and respect prefers-color-scheme for first visit.
Theme toggle
const { theme, setTheme } = useTheme();
<Button variant="ghost" size="icon" onClick={() => setTheme(theme === "dark" ? "light" : "dark")}>
{theme === "dark" ? <Sun /> : <Moon />}
</Button>
For three-state (light / dark / system), use a DropdownMenu.
Audit checklist
Avoid
- Two-tree CSS (
light.css / dark.css) — use a single token system.
- Tailwind colors hardcoded in JSX (
bg-gray-900) when a semantic token exists.
- Forgetting OG/social images that look bad on a dark email client.
Companion: accessibility-pass for contrast verification.