CSS Variables
OKLCH color space variables for better color manipulation and accessibility
Transform your React admin template to match your brand identity with advanced theming capabilities, dynamic color systems, and comprehensive customization options.
Tailwind CSS 4 CSS Variables shadcn/ui Zustand StateThe React template uses a sophisticated theming system that combines CSS custom properties, Zustand state management, and Tailwind CSS 4 for dynamic theme switching and brand customization.
CSS Variables
OKLCH color space variables for better color manipulation and accessibility
Zustand Store
Reactive state management for theme preferences with persistence
Dynamic Switching
Real-time theme updates without page refreshes
The template includes 8 pre-built color themes with both light and dark variants:
// Available theme colors with OKLCH valuesexport const THEME_COLORS: ThemeColorConfig[] = [ { name: 'indigo', color: 'oklch(58.5% 0.233 277.117)', // Light mode darkColor: 'oklch(67.3% 0.182 276.935)', // Dark mode class: 'bg-indigo-500', darkClass: 'bg-indigo-400', }, { name: 'amber', color: 'oklch(76.9% 0.188 70.08)', darkColor: 'oklch(82.8% 0.189 84.429)', class: 'bg-amber-500', darkClass: 'bg-amber-400', }, // Additional colors: emerald, sky, purple, pink, stone, rose]
The theme color state is managed using Zustand with localStorage persistence:
import { create } from 'zustand'import { persist, createJSONStorage } from 'zustand/middleware'
interface ThemeColorState { themeColor: string setThemeColor: (color: string) => void}
export const useThemeColorStore = create<ThemeColorState>()( persist( (set) => ({ themeColor: 'indigo', // Default theme color setThemeColor: (color) => set({ themeColor: color }), }), { name: 'theme-color-storage', storage: createJSONStorage(() => localStorage), } ))
The useThemeColor
hook provides easy access to theme color functionality:
import { useCallback } from 'react'import { useTheme } from '@/store'import { useThemeColorStore } from '@/store/themeColorStore'import { THEME_COLORS, type ThemeColorConfig } from '@/constant/themeColors'
export function useThemeColor() { const { resolvedTheme } = useTheme() const { themeColor, setThemeColor: setThemeColorStore } = useThemeColorStore()
const isDark = resolvedTheme === 'dark'
const setThemeColor = useCallback((themeColorName: string) => { const item = THEME_COLORS.find((item) => item.name === themeColorName) if (item) { setThemeColorStore(themeColorName) const root = document.documentElement
// Update CSS custom properties root.style.setProperty('--primary', isDark ? item.darkColor : item.color) root.style.setProperty('--sidebar-primary', isDark ? item.darkColor : item.color) } }, [isDark, setThemeColorStore])
return { themeColor, setThemeColor, themeColorList: THEME_COLORS, }}
The template includes a comprehensive theme system with system preference detection:
import { create } from 'zustand'import { persist } from 'zustand/middleware'
type Theme = "dark" | "light" | "system"
interface ThemeState { theme: Theme setTheme: (theme: Theme) => void resolvedTheme: "dark" | "light" initialize: () => void}
const getSystemTheme = (): "dark" | "light" => { if (typeof window === 'undefined') return 'light' return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'}
const applyTheme = (theme: Theme) => { const root = window.document.documentElement root.classList.remove("light", "dark")
const resolvedTheme = theme === "system" ? getSystemTheme() : theme root.classList.add(resolvedTheme)
return resolvedTheme}
export const useThemeStore = create<ThemeState>()( persist( (set, get) => ({ theme: "system", resolvedTheme: "light",
setTheme: (theme: Theme) => { const resolvedTheme = applyTheme(theme) set({ theme, resolvedTheme }) },
initialize: () => { const { theme } = get() const resolvedTheme = applyTheme(theme) set({ resolvedTheme })
// Listen for system theme changes if (typeof window !== 'undefined') { const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)') const handleChange = () => { const currentTheme = get().theme if (currentTheme === 'system') { const newResolvedTheme = getSystemTheme() const root = window.document.documentElement root.classList.remove("light", "dark") root.classList.add(newResolvedTheme) set({ resolvedTheme: newResolvedTheme }) } }
mediaQuery.addEventListener('change', handleChange) } }, }), { name: 'admin-theme', partialize: (state) => ({ theme: state.theme }), } ))
The template uses a comprehensive CSS variable system with OKLCH color values:
:root { --radius: 0.65rem;
/* Light theme colors */ --background: oklch(1 0 0); --foreground: oklch(21% 0.034 264.665); --primary: oklch(58.5% 0.233 277.117); --primary-foreground: oklch(0.969 0.016 293.756); --secondary: oklch(0.967 0.001 286.375); --muted: oklch(96.7% 0.003 264.542); --border: oklch(92.8% 0.006 264.531 / 40%);
/* Sidebar specific variables */ --sidebar: oklch(1 0 0); --sidebar-foreground: oklch(0.141 0.005 285.823); --sidebar-primary: oklch(58.5% 0.233 277.117); --sidebar-border: oklch(87.2% 0.01 258.338 / 25%);}
.dark { /* Dark theme colors */ --background: oklch(13% 0.028 261.692); --foreground: oklch(98.5% 0.002 247.839); --primary: oklch(67.3% 0.182 276.935); --sidebar: oklch(0.21 0.006 285.885); --sidebar-primary: oklch(67.3% 0.182 276.935);
/* Additional dark theme variables... */}
The template integrates CSS variables with Tailwind CSS through custom theme configuration:
@theme inline { --radius-sm: calc(var(--radius) - 4px); --radius-md: calc(var(--radius) - 2px); --radius-lg: var(--radius); --radius-xl: calc(var(--radius) + 4px);
--color-background: var(--background); --color-foreground: var(--foreground); --color-primary: var(--primary); --color-primary-foreground: var(--primary-foreground); --color-sidebar: var(--sidebar); --color-sidebar-primary: var(--sidebar-primary);
/* Additional color mappings... */}
The template includes a comprehensive settings panel for theme customization:
import { Sun, Moon, Monitor, Palette, Check, Sidebar, NavigationOff } from 'lucide-react'import { Icon } from '@iconify/react'import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from '@/components/ui/sheet'import { Switch } from '@/components/ui/switch'import { motion } from 'framer-motion'import { useTheme } from '@/store'import { useThemeColor, type ThemeColorInterface } from '@/hooks/useThemeColor'import { useUIStore } from '@/store/uiStore'import { cn } from '@/lib/utils'
export function SettingSheet() { const { setTheme, resolvedTheme } = useTheme() const { themeColor, themeColorList, setThemeColor } = useThemeColor() const { layoutMode, setLayoutMode } = useUIStore()
const isDark = resolvedTheme === 'dark'
const handleChangeThemeMode = (checked: boolean) => { document.documentElement.classList.add('theme-switching') setTheme(checked ? 'dark' : 'light') setTimeout(() => { document.documentElement.classList.remove('theme-switching') }, 0) }
const handleChangeThemeColor = (item: ThemeColorInterface) => { setThemeColor(item.name) }
const handleChangeLayoutMode = (mode: 'sidebar' | 'topbar') => { setLayoutMode(mode) }
return ( <Sheet> <SheetTrigger asChild> <motion.div className="scale-100 hover:scale-105 p-2" animate={{ rotate: 360 }} transition={{ repeat: Infinity, repeatType: 'loop', duration: 15, ease: 'linear' }} > <Icon className="text-xl text-gray-400/70" icon="icon-park-solid:setting" /> </motion.div> </SheetTrigger>
<SheetContent className="w-96 flex flex-col sm:w-[400px]"> <SheetHeader className="pb-6"> <SheetTitle className="text-xl font-semibold flex items-center gap-2"> <Icon icon="lucide:palette" className="text-primary" /> Customize </SheetTitle> </SheetHeader>
<div className="flex flex-col gap-6 pb-6 px-5"> {/* Theme Mode Section */} <motion.div className="space-y-4" initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }}> <div className="flex items-center justify-between"> <h3 className="text-sm font-medium text-foreground flex items-center gap-2"> <Monitor className="w-4 h-4 text-primary" /> Appearance </h3> </div>
<motion.div className="flex items-center justify-between p-3 rounded-lg bg-muted/30 border hover:bg-muted/50" whileHover={{ scale: 1.01 }} > <div className="flex items-center gap-3"> <motion.div animate={{ rotate: isDark ? 180 : 0 }} className="p-2 rounded-full bg-background shadow-sm" > {isDark ? <Moon className="w-4 h-4 text-primary" /> : <Sun className="w-4 h-4 text-primary" />} </motion.div> <span className="text-sm font-medium">{isDark ? 'Dark Mode' : 'Light Mode'}</span> </div> <Switch checked={isDark} onCheckedChange={handleChangeThemeMode} /> </motion.div> </motion.div>
{/* Layout Mode Section */} <motion.div className="space-y-4"> <h3 className="text-sm font-medium text-foreground flex items-center gap-2"> <Monitor className="w-4 h-4 text-primary" /> Layout Mode </h3>
<div className="grid gap-3 grid-cols-2"> {/* Sidebar Layout */} <motion.div className={cn( 'group relative flex flex-col items-center justify-center p-4 rounded-lg border-2 cursor-pointer', layoutMode === 'sidebar' ? 'border-primary bg-primary/5 text-primary' : 'border-border hover:border-border/60' )} onClick={() => handleChangeLayoutMode('sidebar')} whileHover={{ scale: 1.02 }} > <Sidebar className="w-6 h-6 mb-2" /> <span className="text-xs font-medium">Sidebar</span> {layoutMode === 'sidebar' && ( <motion.div className="absolute -top-1 -right-1 w-5 h-5 bg-primary rounded-full flex items-center justify-center"> <Check className="w-3 h-3 text-white" /> </motion.div> )} </motion.div>
{/* Topbar Layout */} <motion.div className={cn( 'group relative flex flex-col items-center justify-center p-4 rounded-lg border-2 cursor-pointer', layoutMode === 'topbar' ? 'border-primary bg-primary/5 text-primary' : 'border-border hover:border-border/60' )} onClick={() => handleChangeLayoutMode('topbar')} whileHover={{ scale: 1.02 }} > <NavigationOff className="w-6 h-6 mb-2" /> <span className="text-xs font-medium">Topbar</span> {layoutMode === 'topbar' && ( <motion.div className="absolute -top-1 -right-1 w-5 h-5 bg-primary rounded-full flex items-center justify-center"> <Check className="w-3 h-3 text-white" /> </motion.div> )} </motion.div> </div> </motion.div>
{/* Color Theme Section */} <motion.div className="space-y-4"> <h3 className="text-sm font-medium text-foreground flex items-center gap-2"> <Palette className="w-4 h-4 text-primary" /> Color Theme </h3>
<div className="grid grid-cols-4 gap-3"> {themeColorList.map((item, index) => ( <motion.div key={item.name} className={cn( 'group relative flex items-center justify-center h-16 rounded-xl border-2 cursor-pointer', themeColor === item.name ? 'border-primary shadow-lg shadow-primary/25' : 'border-border hover:border-border/60' )} onClick={() => handleChangeThemeColor(item)} whileHover={{ scale: 1.05, y: -2 }} initial={{ opacity: 0, scale: 0.8 }} animate={{ opacity: 1, scale: 1 }} transition={{ delay: 0.4 + index * 0.05 }} > <motion.div animate={{ scale: themeColor === item.name ? 1.2 : 1 }} className={cn( 'relative flex items-center justify-center rounded-full shadow-lg', isDark ? item.darkClass : item.class, themeColor === item.name ? 'w-8 h-8' : 'w-6 h-6' )} > {themeColor === item.name && ( <motion.div initial={{ scale: 0 }} animate={{ scale: 1 }}> <Check className="w-4 h-4 text-white" /> </motion.div> )} </motion.div>
{/* Tooltip */} <div className="absolute -bottom-8 left-1/2 transform -translate-x-1/2 px-2 py-1 bg-popover border rounded-md text-xs opacity-0 group-hover:opacity-100"> {item.name} </div> </motion.div> ))} </div> </motion.div> </div> </SheetContent> </Sheet> )}
To add custom color themes, extend the THEME_COLORS
array:
// Add your custom theme colorexport const THEME_COLORS: ThemeColorConfig[] = [ // ... existing colors { name: 'custom-brand', color: 'oklch(65% 0.2 280)', // Your brand color in light mode darkColor: 'oklch(70% 0.15 280)', // Your brand color in dark mode class: 'bg-blue-600', // Tailwind class for light mode darkClass: 'bg-blue-500', // Tailwind class for dark mode }]
Add custom CSS variables for specific use cases:
:root { /* Your custom variables */ --brand-primary: oklch(65% 0.2 280); --brand-secondary: oklch(70% 0.15 120); --custom-gradient: linear-gradient(135deg, var(--brand-primary), var(--brand-secondary));}
.dark { --brand-primary: oklch(70% 0.15 280); --brand-secondary: oklch(75% 0.12 120);}
/* Custom utility classes */.bg-brand-primary { background-color: var(--brand-primary);}
.text-brand-primary { color: var(--brand-primary);}
Override shadcn/ui component styles using CSS variables and Tailwind classes:
/* Override Button component */.btn-custom { @apply bg-brand-primary hover:bg-brand-primary/90; @apply border-brand-primary text-white; @apply shadow-lg hover:shadow-xl; transition: all 0.2s ease-in-out;}
/* Override Card component */.card-custom { @apply bg-gradient-to-br from-background to-muted/50; @apply border-border/50 backdrop-blur-sm; @apply hover:shadow-lg transition-shadow duration-300;}
Create dynamic theme variables that respond to user interactions:
// Utility for dynamic color generationexport function generateThemeVariations(baseColor: string) { const variations = { 50: adjustOklchLightness(baseColor, 0.95), 100: adjustOklchLightness(baseColor, 0.9), 500: baseColor, 900: adjustOklchLightness(baseColor, 0.2), }
return variations}
// Apply dynamic colors to CSS variablesexport function applyDynamicTheme(colorName: string, baseColor: string) { const root = document.documentElement const variations = generateThemeVariations(baseColor)
Object.entries(variations).forEach(([shade, color]) => { root.style.setProperty(`--color-${colorName}-${shade}`, color) })}
Color Accessibility
Ensure sufficient contrast ratios (4.5:1 for normal text, 3:1 for large text) when customizing colors
Performance
Use CSS custom properties for theme switching instead of class-based approaches for better performance
Consistency
Maintain consistent color naming and spacing across your custom theme system
Testing
Test your custom themes in both light and dark modes across different devices
Enable theme debugging for development:
// Add to your development environmentif (process.env.NODE_ENV === 'development') { window.debugTheme = { currentTheme: () => useThemeStore.getState().theme, currentColor: () => useThemeColorStore.getState().themeColor, cssVars: () => { const styles = getComputedStyle(document.documentElement) return { primary: styles.getPropertyValue('--primary'), background: styles.getPropertyValue('--background'), } } }}
Transform your React admin template into a unique, branded experience that perfectly matches your design requirements! 🎨