Theming
Design system, color schemes, and theme customization in WI-CARPOOL
Theme System Overview
WI-CARPOOL uses a comprehensive theming system built on Tailwind CSS with support for light/dark modes and custom color schemes.
🌓 Light/Dark Mode
Automatic theme switching with user preference persistence.
🎨 Custom Colors
Brand-specific color palette with semantic color tokens.
📱 Responsive Design
Consistent theming across all screen sizes and devices.
♿ Accessibility
WCAG compliant color contrasts and accessible design patterns.
Color System
Primary Color Palette
// tailwind.config.ts
export default {
theme: {
extend: {
colors: {
// Brand Colors
primary: {
50: '#eff6ff',
100: '#dbeafe',
200: '#bfdbfe',
300: '#93c5fd',
400: '#60a5fa',
500: '#3b82f6', // Main brand color
600: '#2563eb',
700: '#1d4ed8',
800: '#1e40af',
900: '#1e3a8a',
},
// Semantic Colors
success: {
50: '#f0fdf4',
100: '#dcfce7',
500: '#22c55e',
600: '#16a34a',
},
warning: {
50: '#fffbeb',
100: '#fef3c7',
500: '#f59e0b',
600: '#d97706',
},
error: {
50: '#fef2f2',
100: '#fee2e2',
500: '#ef4444',
600: '#dc2626',
},
}
}
}
}
Dark Mode Configuration
// CSS Custom Properties for theming
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--primary: 221.2 83.2% 53.3%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96%;
--secondary-foreground: 222.2 84% 4.9%;
--muted: 210 40% 96%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96%;
--accent-foreground: 222.2 84% 4.9%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 221.2 83.2% 53.3%;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--primary: 217.2 91.2% 59.8%;
--primary-foreground: 222.2 84% 4.9%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 224.3 76.3% 94.1%;
}
Theme Provider
Theme Context Implementation
// src/components/ThemeProvider.tsx
import { createContext, useContext, useEffect, useState } from "react"
type Theme = "dark" | "light" | "system"
type ThemeProviderProps = {
children: React.ReactNode
defaultTheme?: Theme
storageKey?: string
}
type ThemeProviderState = {
theme: Theme
setTheme: (theme: Theme) => void
}
const ThemeProviderContext = createContext<ThemeProviderState | undefined>(undefined)
export function ThemeProvider({
children,
defaultTheme = "system",
storageKey = "carpool-theme",
...props
}: ThemeProviderProps) {
const [theme, setTheme] = useState<Theme>(
() => (localStorage.getItem(storageKey) as Theme) || defaultTheme
)
useEffect(() => {
const root = window.document.documentElement
root.classList.remove("light", "dark")
if (theme === "system") {
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
.matches
? "dark"
: "light"
root.classList.add(systemTheme)
return
}
root.classList.add(theme)
}, [theme])
const value = {
theme,
setTheme: (theme: Theme) => {
localStorage.setItem(storageKey, theme)
setTheme(theme)
},
}
return (
<ThemeProviderContext.Provider {...props} value={value}>
{children}
</ThemeProviderContext.Provider>
)
}
export const useTheme = () => {
const context = useContext(ThemeProviderContext)
if (context === undefined)
throw new Error("useTheme must be used within a ThemeProvider")
return context
}
Theme Toggle Component
// src/components/ThemeToggle.tsx
import { Moon, Sun } from "lucide-react"
import { Button } from "@/components/ui/button"
import { useTheme } from "@/components/ThemeProvider"
export function ThemeToggle() {
const { theme, setTheme } = useTheme()
const toggleTheme = () => {
if (theme === "light") {
setTheme("dark")
} else if (theme === "dark") {
setTheme("system")
} else {
setTheme("light")
}
}
return (
<Button variant="ghost" size="icon" onClick={toggleTheme}>
{theme === "light" && <Sun className="h-5 w-5" />}
{theme === "dark" && <Moon className="h-5 w-5" />}
{theme === "system" && <Sun className="h-5 w-5" />}
<span className="sr-only">Toggle theme</span>
</Button>
)
}
Component Theming
Button Variants with Theming
// src/components/ui/button.tsx
import { cva } from "class-variance-authority"
const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline: "border border-input hover:bg-accent hover:text-accent-foreground",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "underline-offset-4 hover:underline text-primary",
},
size: {
default: "h-10 py-2 px-4",
sm: "h-9 px-3 rounded-md",
lg: "h-11 px-8 rounded-md",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
Card Component with Theme Support
// src/components/ui/card.tsx
import { cn } from "@/lib/utils"
const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"rounded-lg border bg-card text-card-foreground shadow-sm",
className
)}
{...props}
/>
))
const CardHeader = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex flex-col space-y-1.5 p-6", className)}
{...props}
/>
))
Custom Theme Creation
Creating Custom Themes
// src/themes/custom.ts
export const customThemes = {
ocean: {
primary: {
50: '#ecfeff',
100: '#cffafe',
200: '#a5f3fc',
300: '#67e8f9',
400: '#22d3ee',
500: '#06b6d4',
600: '#0891b2',
700: '#0e7490',
800: '#155e75',
900: '#164e63',
},
secondary: {
50: '#f8fafc',
100: '#f1f5f9',
500: '#64748b',
600: '#475569',
}
},
forest: {
primary: {
50: '#f0fdf4',
100: '#dcfce7',
200: '#bbf7d0',
300: '#86efac',
400: '#4ade80',
500: '#22c55e',
600: '#16a34a',
700: '#15803d',
800: '#166534',
900: '#14532d',
}
}
}
// Usage
export function applyCustomTheme(themeName: keyof typeof customThemes) {
const theme = customThemes[themeName]
const root = document.documentElement
Object.entries(theme.primary).forEach(([shade, color]) => {
root.style.setProperty(`--primary-${shade}`, color)
})
}
Best Practices
Theme Guidelines
- Use semantic color tokens instead of hardcoded colors
- Ensure sufficient contrast ratios for accessibility
- Test themes across different devices and screen sizes
- Provide fallback colors for unsupported browsers
- Use CSS custom properties for dynamic theming
Performance Considerations
- Minimize theme switching animations on mobile
- Use CSS variables instead of JavaScript for color changes
- Implement theme persistence to prevent flash of incorrect theme
- Optimize theme bundle size by removing unused color variants
Accessibility
- Maintain WCAG AA color contrast ratios (4.5:1 for normal text)
- Use focus indicators that work in both light and dark modes
- Provide high contrast mode support
- Test with screen readers and assistive technologies