mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-24 13:54:57 +00:00
17 KiB
17 KiB
Fakemui Theming Guide
Complete guide to theming in Fakemui, including built-in themes, customization, and dynamic theme switching.
Overview
Fakemui's theming system is based on Material Design 3, providing a comprehensive design token system for consistent, accessible, and customizable user interfaces.
Key features:
- 9 built-in themes
- Custom theme creation
- Dynamic theme switching
- Material Design 3 compliance
- Accessibility standards (WCAG AA+)
Built-in Themes
Available Themes
| Theme | Description | Use Case |
|---|---|---|
| default | Primary blue (Material Design 3 default) | General applications |
| light | Light mode optimization | Daytime/bright environments |
| dark | Dark mode optimization | Nighttime/reduced eye strain |
| ocean | Ocean blue with teal accents | Calm, professional applications |
| forest | Green with natural tones | Environmental/wellness apps |
| sunset | Warm orange/red tones | Creative/vibrant applications |
| lavender | Purple with soft tones | Design-focused applications |
| midnight | Deep blue with dark background | Night mode preference |
| rose | Pink/rose tones | Fashion/lifestyle applications |
Theme Structure
Each theme defines:
interface Theme {
palette: {
// Primary colors
primary: { main, light, dark, contrastText }
secondary: { main, light, dark, contrastText }
error: { main, light, dark, contrastText }
warning: { main, light, dark, contrastText }
info: { main, light, dark, contrastText }
success: { main, light, dark, contrastText }
// Neutral colors
background: { default, paper }
text: { primary, secondary, disabled, hint }
divider: color
action: { active, hover, selected, disabled }
// Elevation colors (5 levels)
surface1, surface2, surface3, surface4, surface5
}
typography: {
fontFamily: string
fontSize: number
fontWeightLight: number
fontWeightRegular: number
fontWeightMedium: number
fontWeightBold: number
// Typography variants
h1, h2, h3, h4, h5, h6: TypographyOptions
body1, body2: TypographyOptions
button, caption: TypographyOptions
}
spacing: (multiplier: number) => number
shape: {
borderRadius: number
}
shadows: string[]
transitions: {
duration: { shortest, shorter, standard, complex }
easing: { easeInOut, easeOut, easeIn, linear }
}
}
Using Built-in Themes
Basic Theme Setup
import { ThemeProvider } from '@/fakemui/theming'
import App from './App'
export function Root() {
return (
<ThemeProvider theme="dark">
<App />
</ThemeProvider>
)
}
Reading Current Theme
import { useTheme } from '@/fakemui/theming'
export function ComponentWithTheme() {
const { theme, palette } = useTheme()
return (
<Box sx={{ backgroundColor: palette.primary.main }}>
Current theme: {theme}
</Box>
)
}
Accessing Theme in sx Prop
import { Box, Button } from '@/fakemui'
export function StyledComponent() {
return (
<Box sx={{
// Theme colors
backgroundColor: 'background.paper',
color: 'text.primary',
borderColor: 'divider',
// Theme spacing
padding: 2, // 16px (theme.spacing(2))
gap: 1, // 8px (theme.spacing(1))
// Theme shapes
borderRadius: 1, // 4px
// Elevation/shadows
boxShadow: 1 // theme.shadows[1]
}}>
Content with theme
</Box>
)
}
Creating Custom Themes
Option 1: Extend Existing Theme
import { createTheme } from '@/fakemui/theming'
const customTheme = createTheme({
palette: {
primary: {
main: '#6366f1', // Indigo
light: '#818cf8',
dark: '#4f46e5',
contrastText: '#ffffff'
},
secondary: {
main: '#ec4899', // Pink
light: '#f472b6',
dark: '#db2777',
contrastText: '#ffffff'
}
},
typography: {
fontFamily: "'Inter', sans-serif",
fontSize: 14
}
})
// Use custom theme
<ThemeProvider theme={customTheme}>
<App />
</ThemeProvider>
Option 2: Full Theme Customization
import { createTheme } from '@/fakemui/theming'
const brandTheme = createTheme({
palette: {
primary: {
main: '#2563eb', // Brand blue
light: '#3b82f6',
dark: '#1d4ed8',
contrastText: '#ffffff'
},
secondary: {
main: '#7c3aed', // Brand purple
light: '#a78bfa',
dark: '#6d28d9',
contrastText: '#ffffff'
},
success: {
main: '#10b981',
light: '#34d399',
dark: '#059669',
contrastText: '#ffffff'
},
warning: {
main: '#f59e0b',
light: '#fbbf24',
dark: '#d97706',
contrastText: '#ffffff'
},
error: {
main: '#ef4444',
light: '#f87171',
dark: '#dc2626',
contrastText: '#ffffff'
},
background: {
default: '#ffffff',
paper: '#f9fafb'
},
text: {
primary: 'rgba(0, 0, 0, 0.87)',
secondary: 'rgba(0, 0, 0, 0.60)',
disabled: 'rgba(0, 0, 0, 0.38)',
hint: 'rgba(0, 0, 0, 0.38)'
},
divider: 'rgba(0, 0, 0, 0.12)',
action: {
active: 'rgba(0, 0, 0, 0.54)',
hover: 'rgba(0, 0, 0, 0.04)',
selected: 'rgba(0, 0, 0, 0.08)',
disabled: 'rgba(0, 0, 0, 0.26)'
}
},
typography: {
fontFamily: "'Roboto', sans-serif",
fontSize: 14,
fontWeightLight: 300,
fontWeightRegular: 400,
fontWeightMedium: 500,
fontWeightBold: 700,
h1: {
fontSize: '6rem',
fontWeight: 300,
lineHeight: 1.167,
letterSpacing: '-0.015625em'
},
h2: {
fontSize: '3.75rem',
fontWeight: 300,
lineHeight: 1.2,
letterSpacing: 0
},
h3: {
fontSize: '3rem',
fontWeight: 400,
lineHeight: 1.167,
letterSpacing: '0.0125em'
},
h4: {
fontSize: '2.125rem',
fontWeight: 500,
lineHeight: 1.235,
letterSpacing: 0
},
h5: {
fontSize: '1.5rem',
fontWeight: 500,
lineHeight: 1.334,
letterSpacing: 0
},
h6: {
fontSize: '1.25rem',
fontWeight: 500,
lineHeight: 1.6,
letterSpacing: '0.0125em'
},
body1: {
fontSize: '1rem',
fontWeight: 400,
lineHeight: 1.5,
letterSpacing: '0.03125em'
},
body2: {
fontSize: '0.875rem',
fontWeight: 400,
lineHeight: 1.43,
letterSpacing: '0.0178571429em'
},
button: {
fontSize: '0.875rem',
fontWeight: 500,
lineHeight: 1.75,
letterSpacing: '0.0892857143em',
textTransform: 'uppercase'
},
caption: {
fontSize: '0.75rem',
fontWeight: 500,
lineHeight: 1.67,
letterSpacing: '0.0333333333em'
}
},
shape: {
borderRadius: 4
},
shadows: [
'none',
'0px 2px 1px -1px rgba(0,0,0,0.2)',
'0px 3px 1px -2px rgba(0,0,0,0.2)',
'0px 3px 3px -2px rgba(0,0,0,0.2)',
'0px 2px 4px -1px rgba(0,0,0,0.2)',
'0px 3px 5px -1px rgba(0,0,0,0.2)'
],
transitions: {
duration: {
shortest: 150,
shorter: 200,
standard: 300,
complex: 375
},
easing: {
easeInOut: 'cubic-bezier(0.4, 0, 0.2, 1)',
easeOut: 'cubic-bezier(0.0, 0, 0.2, 1)',
easeIn: 'cubic-bezier(0.4, 0, 1, 1)',
linear: 'linear'
}
}
})
<ThemeProvider theme={brandTheme}>
<App />
</ThemeProvider>
Dynamic Theme Switching
Basic Theme Switcher
import { Box, Button, Stack } from '@/fakemui'
import { useTheme } from '@/fakemui/theming'
export function ThemeSwitcher() {
const { theme, setTheme } = useTheme()
const themes = ['default', 'dark', 'ocean', 'forest', 'sunset']
return (
<Box>
<p>Current theme: {theme}</p>
<Stack direction="row" spacing={1}>
{themes.map((t) => (
<Button
key={t}
variant={theme === t ? 'contained' : 'outlined'}
onClick={() => setTheme(t)}
>
{t}
</Button>
))}
</Stack>
</Box>
)
}
Theme Switcher in AppBar
import { AppBar, Toolbar, Box, Select } from '@/fakemui'
import { useTheme } from '@/fakemui/theming'
export function Header() {
const { theme, setTheme } = useTheme()
return (
<AppBar position="static">
<Toolbar>
<Box sx={{ flexGrow: 1 }}>MyApp</Box>
<Select
value={theme}
onChange={(e) => setTheme(e.target.value)}
options={[
{ label: 'Default', value: 'default' },
{ label: 'Dark', value: 'dark' },
{ label: 'Ocean', value: 'ocean' }
]}
sx={{ color: 'white', minWidth: 120 }}
/>
</Toolbar>
</AppBar>
)
}
Persist Theme Preference
import { useTheme } from '@/fakemui/theming'
import { useEffect } from 'react'
export function App() {
const { theme, setTheme } = useTheme()
// Load theme from localStorage
useEffect(() => {
const savedTheme = localStorage.getItem('app-theme')
if (savedTheme) {
setTheme(savedTheme)
}
}, [])
// Save theme to localStorage
useEffect(() => {
localStorage.setItem('app-theme', theme)
}, [theme])
return <AppContent />
}
System Preference Detection
import { useTheme } from '@/fakemui/theming'
import { useEffect } from 'react'
export function App() {
const { setTheme } = useTheme()
useEffect(() => {
// Check system preference
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)')
const handleChange = (e) => {
setTheme(e.matches ? 'dark' : 'default')
}
// Set initial theme based on system preference
setTheme(prefersDark.matches ? 'dark' : 'default')
// Listen for changes
prefersDark.addEventListener('change', handleChange)
return () => prefersDark.removeEventListener('change', handleChange)
}, [setTheme])
return <AppContent />
}
Color System
Material Design 3 Color Roles
// Core colors
primary // Primary interactive color
secondary // Secondary interactive color
tertiary // Tertiary interactive color (accent)
// Semantic colors
error // Error/destructive actions
warning // Warning messages
info // Informational messages
success // Success confirmations
// Neutral colors
background // Background surface
surface // Surface elements (cards, containers)
text // Text content
outline // Borders, dividers
Using Colors in Components
import { Button, Card, Typography, Box } from '@/fakemui'
export function ColorDemo() {
return (
<Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 2 }}>
{/* Primary variants */}
<Button color="primary" variant="contained">Primary</Button>
<Button color="primary" variant="outlined">Primary</Button>
<Button color="primary" variant="text">Primary</Button>
{/* Secondary variants */}
<Button color="secondary" variant="contained">Secondary</Button>
<Button color="secondary" variant="outlined">Secondary</Button>
<Button color="secondary" variant="text">Secondary</Button>
{/* Semantic colors */}
<Button color="error" variant="contained">Delete</Button>
<Button color="warning" variant="contained">Warning</Button>
<Button color="success" variant="contained">Success</Button>
{/* In cards */}
<Card sx={{ p: 2, backgroundColor: 'success.light' }}>
<Typography color="success.main">Success message</Typography>
</Card>
</Box>
)
}
Material Design 3 Token Reference
Spacing
// 8px base unit
sx={{
p: 1 // 8px
p: 2 // 16px
p: 3 // 24px
p: 4 // 32px
gap: 0.5 // 4px
}}
Elevation/Shadows
// 5 elevation levels
boxShadow: 0 // none
boxShadow: 1 // low
boxShadow: 2 // low-medium
boxShadow: 3 // medium
boxShadow: 4 // medium-high
boxShadow: 5 // high
Typography
// 6 heading levels
variant="h1" // Display large
variant="h2" // Display medium
variant="h3" // Display small
variant="h4" // Headline large
variant="h5" // Headline medium
variant="h6" // Headline small
// Body text
variant="body1" // Body large
variant="body2" // Body medium
// Special
variant="button" // Button text
variant="caption" // Caption
Border Radius
// All components support borderRadius
sx={{ borderRadius: 1 }} // 4px
sx={{ borderRadius: 1.5 }} // 6px
sx={{ borderRadius: 2 }} // 8px
sx={{ borderRadius: '50%' }} // Circular
Responsive Design
Breakpoints
Material Design 3 defines responsive breakpoints:
// Mobile-first breakpoints
xs: 0 // Extra small (phones)
sm: 600 // Small (tablets)
md: 960 // Medium (small laptops)
lg: 1264 // Large (laptops)
xl: 1904 // Extra large (desktops)
Using Breakpoints in sx
<Box sx={{
fontSize: '1rem',
'@media (min-width: 600px)': {
fontSize: '1.25rem'
},
'@media (min-width: 960px)': {
fontSize: '1.5rem'
}
}}>
Responsive text
</Box>
Responsive Grid
<Grid container spacing={2}>
<Grid item xs={12} sm={6} md={4} lg={3}>
Full width on mobile
Half width on tablet
Third width on medium
Quarter width on large
</Grid>
</Grid>
Accessibility in Theming
Contrast Ratios
All theme colors maintain WCAG AA+ contrast:
// Light text on dark background
color: 'text.primary' // 87% opacity = 4.5:1 ratio
// Dark text on light background
color: 'text.primary' // 87% opacity = 14:1 ratio
Color Blindness Support
Fakemui themes avoid red-green only differentiation:
// ✅ Good - uses multiple visual cues
<Alert severity="error" icon={<ErrorIcon />}>
Error message
</Alert>
// ❌ Avoid - relies only on red color
<div style={{ color: 'red' }}>Error</div>
High Contrast Mode
const highContrastTheme = createTheme({
palette: {
text: {
primary: '#000000', // Full black
secondary: '#333333' // Darker gray
},
background: {
default: '#ffffff', // Full white
paper: '#f5f5f5'
}
}
})
Common Theming Patterns
Dark Mode Toggle
import { IconButton, useTheme } from '@/fakemui'
import { DarkModeIcon, LightModeIcon } from '@/fakemui/icons'
export function DarkModeToggle() {
const { theme, setTheme } = useTheme()
const isDark = theme === 'dark'
return (
<IconButton
onClick={() => setTheme(isDark ? 'default' : 'dark')}
color="inherit"
>
{isDark ? <LightModeIcon /> : <DarkModeIcon />}
</IconButton>
)
}
Brand Customization
const createBrandTheme = (brandColor) => {
return createTheme({
palette: {
primary: {
main: brandColor,
light: lighten(brandColor, 0.3),
dark: darken(brandColor, 0.3),
contrastText: '#ffffff'
}
}
})
}
Accessibility Overrides
const accessibilityTheme = createTheme({
palette: {
text: {
primary: '#000000',
secondary: '#404040',
disabled: '#808080'
},
action: {
hover: 'rgba(0, 0, 0, 0.08)',
selected: 'rgba(0, 0, 0, 0.16)'
}
},
typography: {
fontSize: 16, // Larger base font
h6: {
fontSize: '1.5rem' // Larger headings
}
}
})
Best Practices
- Maintain sufficient contrast - Aim for WCAG AA+ (4.5:1)
- Use semantic colors - Use
error,success,warningfor meaning - Don't rely on color alone - Combine with icons/text
- Test with color blindness simulators - Chrome DevTools has built-in tool
- Support system preferences - Detect
prefers-color-scheme - Persist user choice - Save theme in localStorage
- Use theme tokens - Avoid hardcoded colors
- Document custom themes - Include usage examples
- Test on real devices - Colors vary across screens
- Follow Material Design 3 - Use official specifications
Troubleshooting
Theme not applying
// ✅ Correct - Wrap app in ThemeProvider
<ThemeProvider theme="dark">
<App />
</ThemeProvider>
// ❌ Wrong - Missing wrapper
<App />
Colors not updating
// ✅ Use useTheme hook
const { setTheme } = useTheme()
setTheme('dark')
// ❌ Don't set directly
window.fakemui.theme = 'dark' // Won't work
Custom theme not working
// ✅ Pass theme object to ThemeProvider
const customTheme = createTheme({ /* ... */ })
<ThemeProvider theme={customTheme}>
// ❌ Pass theme string (only works for built-in themes)
<ThemeProvider theme={customTheme}> // Won't work
Resources
- Material Design 3 Official Guide
- Material Design 3 Color System: https://m3.material.io/styles/color/overview
- Material Design 3 Typography: https://m3.material.io/styles/typography/overview
- WCAG Contrast Guidelines: https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html