# Theming & Dark Mode
Alga PSA supports light/dark themes via `next-themes` with class-based switching. The `` element gets `.dark` or `.light`, activating CSS variable sets in `globals.css`. All colors flow through CSS custom properties referenced by Tailwind semantic tokens.
## Coding Standards
### Do
1. **Use Tailwind semantic tokens** — `bg-primary-50`, `text-foreground`, `border-border`, not raw colors
2. **Use CSS variables** for any custom styling — `rgb(var(--color-text-700))` not `#334155`
3. **Adapt dynamic colors** with `adaptColorsForDarkMode()` for user/entity-generated colors (tags, avatars, etc.)
4. **Use the hydration-safe pattern** when reading `resolvedTheme` in components — track a `mounted` state via `useEffect` and only read the theme after mount to avoid SSR mismatches
5. **Test both themes** — toggle dark mode and verify contrast, readability, borders
6. **Add dark overrides in `globals.css`** when integrating new third-party components
7. **Use `useAppTheme`** from `@alga-psa/ui/hooks` — not `useTheme` from next-themes directly (includes DB sync + preference persistence)
### Don't
1. **Hardcode hex/rgb** in components — won't respond to theme changes
2. **Use `bg-white`/`bg-black`** — use `bg-background` or `bg-card` (existing `bg-white` has a global override but new code should use tokens)
3. **Forget `suppressHydrationWarning`** on ``/`
` if modifying root layout
4. **Import `useTheme` directly** from next-themes — use `useAppTheme` instead
### Common Mistakes
**Wrong** — hardcoded background:
```tsx
```
**Fix** — use token:
```tsx
```
**Wrong** — inline color without dark variant:
```tsx
```
**Fix** — CSS variable:
```tsx
```
## CSS Variable System
Colors are space-separated RGB triples in `server/src/app/globals.css` under `.light`/`.dark`. Tailwind references them via `rgb(var(--color-*))` in `server/tailwind.config.ts` (`darkMode: 'class'`).
In dark mode, 50-900 scales are **inverted** (low=dark, high=light), so `bg-primary-50` is always the subtlest background regardless of theme.
**Token groups**: `--color-background`, `--color-card`, `--color-border-{50-900}`, `--color-text-{50-900}`, `--color-primary/secondary/accent-{50-900}`, `--color-status-success/warning/error`, `--badge-*` (semantic, branding-independent), `--color-sidebar-*`, `--alga-*` (semantic aliases).
**Global dark overrides** in `globals.css` remap common utilities (`bg-white`, `text-gray-700`, `border-slate-200`) to CSS variables. Also overrides for react-day-picker, react-big-calendar, Tiptap/ProseMirror.
## Provider Chain
```
AppThemeProvider → ThemeBridge → BrandingProvider
```
| Provider | File | Role |
|---|---|---|
| `AppThemeProvider` | `server/src/components/providers/AppThemeProvider.tsx` | Wraps next-themes (`attribute="class"`, `defaultTheme="system"`, `disableTransitionOnChange`). Accepts optional `forcedTheme` prop to lock a specific theme (disables system detection). Injects DB persistence via `ThemeActionsProvider`. |
| `ThemeBridge` | `server/src/components/providers/ThemeBridge.tsx` | Syncs `resolvedTheme` to Mantine (`forceColorScheme`) and Radix (`appearance`). Sets `data-theme` on ``. Hides content until mounted (FOUC prevention). |
| `BrandingProvider` | `packages/tenancy/src/components/providers/BrandingProvider.tsx` | Generates CSS var overrides from tenant branding. `.dark {}` block with inverted shades. Checks for server-injected styles to avoid duplication. |
Used in: MSP layout (`server/src/app/layout.tsx`), client portal (`server/src/app/client-portal/ClientPortalLayoutClient.tsx`), client auth (`server/src/app/auth/client-portal/layout.tsx`), MSP auth (`server/src/app/auth/layout.tsx` — with `forcedTheme="light"`).
## Theme Toggle & Persistence
- **`ThemeToggle`** (`packages/ui/src/components/ThemeToggle.tsx`): Light/Dark/System dropdown. Returns `null` when flag disabled. IDs: `theme-toggle`, `data-automation-id="theme-toggle"`.
- **`useAppTheme`** (`packages/ui/src/hooks/useAppTheme.tsx`): Extends next-themes with DB persistence + feature flag check. `lastSyncedTheme` ref prevents circular saves.
**Flow**: Toggle → next-themes sets class → `useAppTheme` calls `updateThemePreferenceAction` → upserts `user_preferences` table (`setting_name='theme'`). On login, loads via `getThemePreferenceAction`.
**Server actions** (`packages/users/src/actions/user-actions/themeActions.ts`): `getThemePreferenceAction` (withOptionalAuth), `updateThemePreferenceAction` (withAuth).
## Branding + Dark Mode
Tenant colors override primary/secondary CSS vars. Dark mode inverts shade scales (`packages/tenancy/src/lib/generateBrandingStyles.ts`): `invertShades()` flips 50<->900, 100<->800, etc. (400/500 stay). FOUC prevention: server-side `