Theme system standard
43 oklch color themes via tweakcn. Theme switching, CSS variable architecture, dark mode.
Theme system standard
Purpose: Defines the multi-theme architecture that ships with every prototype, covering 43 color themes with light/dark variants, CSS variable structure, and font loading.
Every prototype ships with professional theming. 43 themes transform the entire experience.
Principle
Theme selection transforms the full visual experience -- background, cards, borders, text, accents, fonts, and border radius. A theme that only changes the primary color is not a real theme; it's a color accent.
What's included
The prototype scaffold ships with 43 color themes (1 default + 42 from tweakcn), each with light and dark variants:
| # | Theme | # | Theme | # | Theme |
|---|---|---|---|---|---|
| 1 | Default | 16 | Quantum Rose | 31 | Midnight Bloom |
| 2 | Modern Minimal | 17 | Nature | 32 | Candyland |
| 3 | Violet Bloom | 18 | Bold Tech | 33 | Northern Lights |
| 4 | T3 Chat | 19 | Elegant Luxury | 34 | Vintage Paper |
| 5 | 20 | Amber Minimal | 35 | Sunset Horizon | |
| 6 | Mocha Mousse | 21 | Supabase | 36 | Starry Night |
| 7 | Bubblegum | 22 | Neo Brutalism | 37 | Claude |
| 8 | Amethyst Haze | 23 | Solar Dusk | 38 | Vercel |
| 9 | Notebook | 24 | Claymorphism | 39 | Darkmatter |
| 10 | Doom 64 | 25 | Cyberpunk | 40 | Mono |
| 11 | Catppuccin | 26 | Pastel Dreams | 41 | Soft Pop |
| 12 | Graphite | 27 | Clean Slate | 42 | Sage Garden |
| 13 | Perpetuity | 28 | Caffeine | ||
| 14 | Kodama Grove | 29 | Ocean Breeze | ||
| 15 | Cosmic Night | 30 | Retro Arcade |
Total combinations: 86 (43 themes x 2 modes).
Attribution
Theme presets adapted from tweakcn by @jnsahaj, licensed under Apache 2.0.
How it works
- Light/dark mode —
next-themesmanages thedarkclass on<html> - Color themes — Runtime CSS custom properties applied via
document.documentElement.style - Independent and composable — Selecting a color theme + toggling dark mode work together seamlessly
- Persisted via localStorage — Key:
theme-color - Fonts load lazily — Google Fonts CDN links are injected only when a theme using them is selected
Architecture
┌─────────────────────────────────────────────┐
│ ThemeProvider (layout.tsx) │
│ ├─ NextThemesProvider (light/dark/system) │
│ └─ ColorThemeProvider (43 color themes) │
│ ├─ reads: lib/themes/presets.ts │
│ ├─ calls: lib/themes/apply-theme.ts │
│ └─ calls: lib/themes/fonts.ts │
└─────────────────────────────────────────────┘
| File | Purpose |
|---|---|
lib/themes/types.ts |
TypeScript interfaces for theme system |
lib/themes/presets.ts |
All 43 theme definitions (colors, radius, fonts) |
lib/themes/apply-theme.ts |
Runtime CSS variable application via document.documentElement.style |
lib/themes/fonts.ts |
Lazy Google Fonts CDN loading |
components/theme-provider.tsx |
Combined provider: NextThemesProvider + ColorThemeProvider |
components/theme-picker.tsx |
Theme selection UI (scrollable list, search, swatches) |
components/mode-toggle.tsx |
Light/dark/system toggle (unchanged) |
app/globals.css |
Default :root / .dark variable definitions |
Color format
Theme presets use hex color values (the format used by tweakcn).
The default :root / .dark variables in globals.css use oklch (shadcn defaults).
When a color theme is active, the hex values override the oklch defaults at runtime.
CSS variables
Each theme defines 32 CSS custom properties:
--background,--foreground— Page background and text--card,--card-foreground— Card surfaces--popover,--popover-foreground— Dropdown/popover surfaces--primary,--primary-foreground— Primary actions--secondary,--secondary-foreground— Secondary surfaces--muted,--muted-foreground— Muted/subtle elements--accent,--accent-foreground— Accent highlights--destructive,--destructive-foreground— Destructive/danger actions--border,--input,--ring— Borders, inputs, focus rings--chart-1through--chart-5— Chart colors--sidebar,--sidebar-foreground, etc. — Sidebar-specific colors--radius— Border radius
Font system
Some themes include custom Google Fonts (sans, serif, mono).
Fonts are loaded lazily -- the <link> tag is injected into <head> only when a theme that uses them is selected.
Font families already loaded are tracked to prevent duplicate requests.
Prototype phase checklist
- Does the feature render correctly in the Default theme?
- Spot-check at least 3 other themes (e.g., Neo Brutalism, Ocean Breeze, Claude)
- Does it work in both light and dark modes?
- Are all interactive elements visible and accessible in all theme/mode combinations?
- Do custom colors (if any) meet WCAG AA contrast in all themes?
- Is the theme picker visible and functional?
Adding custom brand themes
- Add a new
ThemePresetobject — Inlib/themes/presets.ts - Define all 32 CSS variable values — For both
lightanddarkvariants - Assign custom fonts — Optionally via the
fontsfield - Add the new preset — To the
themePresetsarray export - Test in both modes — Light and dark
- Verify contrast ratios — WCAG AA compliance
Example
const my_brand: ThemePreset = {
id: "my-brand",
label: "My Brand",
radius: "0.5rem",
fonts: {
sans: "Inter, sans-serif",
},
light: {
background: "#ffffff",
foreground: "#1a1a1a",
primary: "#3b82f6",
// ... all 32 variables
},
dark: {
background: "#0a0a0a",
foreground: "#fafafa",
primary: "#60a5fa",
// ... all 32 variables
},
}
Theme System Standard -- March 2026