# 08 — Theming infrastructure (multi-theme + dark mode) ## Goal Set up a CSS-variable-based theme system that supports multiple named themes × `{light, dark}`, persists per user, and switches on the fly without a flash of unstyled content. ## Why Cheap to set up before modules exist, painful to retrofit afterwards. Locking in the structure now means every later component "just works" with theming, and adding a new theme is a CSS block — no code change. ## Depends on - 02 (Next.js skeleton, shadcn already initialized) - 07 (users table, so we can add columns) ## Scope ### Schema additions (new migration in `src/modules/_core/schema.ts`) - `users.theme` — text, default `'default'`. - `users.theme_mode` — text, default `'system'`, check constraint `('light', 'dark', 'system')`. ### CSS structure (`src/app/globals.css`) Use shadcn's CSS-variable conventions, scoped by `data-theme` on ``: ```css :root { /* default light tokens */ } .dark { /* default dark tokens */ } [data-theme="warm"] { /* warm light */ } [data-theme="warm"].dark { /* warm dark */ } ``` Ship at least **two** themes (`default` + one more) so the architecture is actually exercised. Token values can be placeholder — refining the palettes is a separate later concern. ### Theme registry (`src/modules/_core/themes.ts`) ```ts export type ThemeMode = "light" | "dark" | "system"; export type ThemeId = "default" | "warm" | string; export const THEMES: ReadonlyArray<{ id: ThemeId; label: string }> = [ { id: "default", label: "Default" }, { id: "warm", label: "Warm" }, ]; ``` Picker reads from this list — no hardcoded options in the UI. ### Root layout (server component) - Reads the current session; pulls `theme` + `theme_mode`. - Renders `` on first paint. **No flash.** - For the `system` mode, the resolved value comes from a small inline `