WFDASH-WEBAPP/docs/theme-preview/README.md
SJCoetz 7df7a8850f feat(item-history): add getItemHistory service with 5 PB queries
Co-authored-by: Orca <help@stably.ai>
2026-07-29 14:51:58 +02:00

3.9 KiB

WFDASH — Multi-theme system

Ship 8 selectable themes (5 standard + 3 standout) with a warm/dark mode toggle. Theme selection persists per browser. No FOUC on reload.

File map

src/
├── lib/
│   ├── themes.ts                 # 8-theme registry (id, label, fonts, swatches)
│   └── use-theme-id.ts           # Client hook to read/write active theme
├── styles/
│   └── themes.css                # OKLCH token blocks for all 8 themes (warm + dark)
├── components/
│   ├── theme-provider.tsx        # next-themes wrapper (manages warm/dark mode)
│   ├── theme-bootstrap.tsx       # Inline <head> script — prevents FOUC
│   ├── theme-picker.tsx          # Settings UI — mini preview cards
│   └── sidebar.tsx               # +Settings nav item
└── app/
    ├── layout.tsx                # +Inter, Space Grotesk, DM Sans, Playfair,
    │                             #  Lato, Nunito, Outfit, JetBrains Mono via next/font
    ├── globals.css               # Imports themes.css
    └── (app)/settings/page.tsx   # New Settings page with Appearance section

The 8 themes

# ID Category Fonts Radius Notes
1 crisp Standard Inter 10px Default. Calm, professional.
2 midnight Standard Space Grotesk + DM Sans 12px Dark control-room.
3 editorial Standard Playfair Display + Lato 6px Warm cream + serif.
4 bento Standard Inter 18px Tinted tiles, big radius.
5 field-ops Standard Inter 8px Dense, navy sidebar, gold CTA.
6 terminal Standout JetBrains Mono 0px Green CRT, scanlines, mono.
7 blueprint Standout JetBrains Mono + Inter 2px White-on-blue, grid, technical.
8 confetti Standout Outfit + Nunito 20px Pastel, playful, oversized radius.

How it works

Theme identity = data-theme="<id>" on <html>

  • Set by theme-bootstrap.tsx before hydration (prevents flash)
  • Updated by useThemeId() hook when user picks a new theme
  • Persisted to localStorage["wfdash-theme"]

Mode (warm/dark) = .warm or .dark class on <html>

  • Managed by next-themes (already wired in theme-provider.tsx)
  • Persisted to localStorage["wfdash-mode"]
  • Independent from theme — you can mix any theme with any mode

shadcn/ui adapts automatically because all components read from CSS vars (--background, --primary, --accent, --radius, etc.) that themes.css overrides per theme.

How to add a new theme

  1. Add an entry to THEMES in src/lib/themes.ts
  2. Add a [data-theme="<id>"] block in src/styles/themes.css (copy an existing theme and adjust tokens). Add .dark variant if it works in dark mode.
  3. Add radiusMap[<id>] in theme-bootstrap.tsx
  4. Done — picker picks it up automatically

How to ship a font swap

Each theme has fontHeading + fontBody pointing at a CSS var. Add the font to src/app/layout.tsx via next/font/google and register its .variable. Then add the binding in src/styles/themes.css:

[data-theme="<id>"] {
  --font-heading: var(--font-<your-font>);
  --font-body: var(--font-<your-font>);
}

The body picks up var(--font-body) automatically; font-heading-theme class applies heading font where you need it.

Standout theme caveats

The 3 standout themes override default component styling:

  • Terminal — sharp 0px radius, uppercase headings, mono everywhere, scanline body overlay
  • Blueprint — sharp 2px radius, uppercase headings, grid overlay, transparent inputs
  • Confetti — 20px radius (capped — shadcn's --radius-4xl calc clamps at ~1.6rem)

All three are still built on the brand palette (#4C96D7 / #F2AF58 / #E94E49) but applied in different ways. They're tested against the standard shadcn components but may render slightly differently from the 5 standard themes — by design.