How Theming Works
Understanding the tweakcn/theme-picker theme system: OKLCH colors, CSS variables, light/dark modes, and custom typography.
Theme Naming Convention
Each theme has two variants: light and dark. The theme value follows this pattern:
[theme-name]-light[theme-name]-darkExamples:catppuccin-light // Catppuccin light modecatppuccin-dark // Catppuccin dark modeclaude-light // Claude light modeclaude-dark // Claude dark modeOKLCH Color System
tweakcn/theme-picker uses the OKLCH color space for all theme colors. OKLCH provides perceptually uniform colors, meaning colors with the same lightness value actually appear equally bright.
oklch(L C H)1/* Primary color in OKLCH */2--primary: oklch(0.55 0.25 297.02); /* L=0.55, C=0.25, H=297° */3--primary-foreground: oklch(1 0 0); /* Pure white */45/* The same hue with different lightness */6--primary-light: oklch(0.75 0.18 297); /* Lighter version */7--primary-dark: oklch(0.35 0.20 297); /* Darker version */CSS Variables
Themes define a comprehensive set of CSS custom properties that shadcn/ui components use:
Color Variables
--background/--foreground--card/--card-foreground--popover/--popover-foreground--primary/--primary-foreground--secondary/--secondary-foreground--muted/--muted-foreground--accent/--accent-foreground--destructive/--destructive-foreground--border,--input,--ring
Other Variables
--radius- Border radius base--font-sans- Sans-serif font stack--font-serif- Serif font stack--font-mono- Monospace font stack--shadow-*- Shadow definitions--chart-1to--chart-5--sidebar-*- Sidebar-specific colors
Light and Dark Mode
Each theme defines variables for both modes using CSS selectors:
1/* Light mode - using data-theme attribute */2[data-theme="catppuccin-light"] {3 --background: oklch(0.96 0.01 264.53);4 --foreground: oklch(0.44 0.04 279.33);5 --primary: oklch(0.55 0.25 297.02);6 /* ... more variables */7}89/* Dark mode */10[data-theme="catppuccin-dark"] {11 --background: oklch(0.22 0.03 284.06);12 --foreground: oklch(0.88 0.04 272.28);13 --primary: oklch(0.79 0.12 304.77);14 /* ... more variables */15}Light Mode
Higher lightness values for backgrounds, lower for text. Subtle shadows and borders.
Dark Mode
Lower lightness for backgrounds, higher for text. Often more saturated accent colors.
Typography
Each theme includes carefully selected Google Fonts that complement the color scheme:
1/* Font variables in theme CSS */2--font-sans: Montserrat, sans-serif;3--font-serif: Georgia, serif;4--font-mono: Fira Code, monospace;56/* Applied in Tailwind */7body {8 font-family: var(--font-sans);9}Loading Google Fonts
Make sure to load the required fonts. Add this to your layout:
<link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700&display=swap" rel="stylesheet">Switching Themes Programmatically
Use the useTheme hook from next-themes to control themes programmatically in your components.
The useTheme Hook
1import { useTheme } from "next-themes"Return Values
| Property | Type | Description |
|---|---|---|
| theme | string | Current active theme (e.g., "catppuccin-dark") |
| setTheme | (theme: string) => void | Function to change the current theme |
| themes | string[] | List of all available themes |
| resolvedTheme | string | Actual theme applied (useful when theme is "system") |
| systemTheme | "light" | "dark" | System preference (light or dark) |
Basic Usage
1const { theme, setTheme } = useTheme()23// theme = "catppuccin-dark"4console.log(theme)Parsing Theme Values
Since themes follow the [name]-[mode] pattern, you can parse them to get the color theme and mode separately:
1const { theme } = useTheme()23// Extract the color theme name4const colorTheme = theme?.replace("-light", "").replace("-dark", "")5// colorTheme = "catppuccin"67// Check if dark mode8const isDark = theme?.endsWith("-dark")9// isDark = trueSetting Themes
1const { setTheme } = useTheme()23// Set a specific theme with mode4setTheme("claude-dark")5setTheme("vercel-light")6setTheme("catppuccin-dark")78// Set theme dynamically9const themeName = "cyberpunk"10const mode = "dark"11setTheme(`${themeName}-${mode}`)Toggling Light/Dark Mode
To toggle between light and dark while keeping the same color theme:
1const { theme, setTheme } = useTheme()23const toggleMode = () => {4 // Get current color theme (without -light/-dark suffix)5 const colorTheme = theme?.replace("-light", "").replace("-dark", "")67 // Check current mode8 const isDark = theme?.endsWith("-dark")910 // Toggle to opposite mode11 setTheme(`${colorTheme}-${isDark ? "light" : "dark"}`)12}1314// If theme is "catppuccin-dark", calling toggleMode()15// will change it to "catppuccin-light"Switching Color Themes
To switch to a different color theme while preserving the current mode:
1const { theme, setTheme } = useTheme()23const switchColorTheme = (newTheme: string) => {4 // Keep the current mode (light/dark)5 const isDark = theme?.endsWith("-dark")67 // Apply new theme with same mode8 setTheme(`${newTheme}-${isDark ? "dark" : "light"}`)9}1011// Usage12switchColorTheme("claude") // Switches to claude-[current-mode]13switchColorTheme("vercel") // Switches to vercel-[current-mode]14switchColorTheme("cyberpunk") // Switches to cyberpunk-[current-mode]Getting Available Themes
You can also import the theme configuration directly:
1import { themes, sortedThemes, allThemeValues } from "@/lib/themes-config"23// themes - Array of theme objects with metadata4themes.forEach(t => {5 console.log(t.name) // "catppuccin"6 console.log(t.title) // "Catppuccin"7 console.log(t.description) // "Soothing pastel theme..."8 console.log(t.category) // "colorful"9})1011// sortedThemes - Alphabetically sorted (default first)12// allThemeValues - All theme strings: ["default-light", "default-dark", ...]⚠ Hydration Warning
The theme is stored in localStorage and isn't available during SSR. Always check if the component is mounted before rendering theme-dependent UI:
const [mounted, setMounted] = useState(false)const { theme } = useTheme()useEffect(() => { setMounted(true)}, [])// Avoid hydration mismatchif (!mounted) return null// Safe to use theme nowreturn <div>Current: {theme}</div>