mui-theming
MUI Theming Skill
createTheme() API
The createTheme function is the entry point for all MUI customization. It accepts a deeply partial theme object and merges it with the defaults.
import { createTheme } from '@mui/material/styles';
const theme = createTheme({
palette: { ... },
typography: { ... },
spacing: 8, // base spacing unit in px (default: 8)
shape: { borderRadius: 8 },
breakpoints: { ... },
shadows: [...],
transitions: { ... },
zIndex: { ... },
});
Palette
Standard palette colors
const theme = createTheme({
palette: {
primary: {
light: '#757ce8',
main: '#3f50b5',
dark: '#002884',
contrastText: '#fff',
},
secondary: {
light: '#ff7961',
main: '#f44336',
dark: '#ba000d',
contrastText: '#000',
},
error: { main: '#d32f2f' },
warning: { main: '#ed6c02' },
info: { main: '#0288d1' },
success: { main: '#2e7d32' },
},
});
MUI auto-generates light, dark, and contrastText from main if you omit them.
Custom palette colors with augmentColor
const theme = createTheme({
palette: {
// augmentColor adds light/dark/contrastText automatically
neutral: theme.palette.augmentColor({
color: { main: '#64748b' },
name: 'neutral',
}),
brand: theme.palette.augmentColor({
color: { main: '#6366f1', light: '#818cf8', dark: '#4f46e5' },
name: 'brand',
}),
},
});
Use a two-step createTheme call when augmentColor needs the base theme:
let theme = createTheme();
theme = createTheme(theme, {
palette: {
salmon: theme.palette.augmentColor({
color: { main: '#FF5733' },
name: 'salmon',
}),
},
});
Background and text
palette: {
background: {
default: '#f5f5f5',
paper: '#ffffff',
},
text: {
primary: 'rgba(0,0,0,0.87)',
secondary: 'rgba(0,0,0,0.6)',
disabled: '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)',
disabledBackground: 'rgba(0,0,0,0.12)',
focus: 'rgba(0,0,0,0.12)',
},
},
Typography
const theme = createTheme({
typography: {
fontFamily: '"Inter", "Roboto", "Helvetica", "Arial", sans-serif',
fontSize: 14, // base font size (rem calculation root)
htmlFontSize: 16, // <html> font size for rem calculations
fontWeightLight: 300,
fontWeightRegular: 400,
fontWeightMedium: 500,
fontWeightBold: 700,
h1: { fontSize: '2.5rem', fontWeight: 700, lineHeight: 1.2, letterSpacing: '-0.01562em' },
h2: { fontSize: '2rem', fontWeight: 700, lineHeight: 1.3 },
h3: { fontSize: '1.75rem', fontWeight: 600, lineHeight: 1.3 },
h4: { fontSize: '1.5rem', fontWeight: 600, lineHeight: 1.4 },
h5: { fontSize: '1.25rem', fontWeight: 600, lineHeight: 1.5 },
h6: { fontSize: '1rem', fontWeight: 600, lineHeight: 1.6 },
subtitle1: { fontSize: '1rem', fontWeight: 400, lineHeight: 1.75 },
subtitle2: { fontSize: '0.875rem', fontWeight: 500, lineHeight: 1.57 },
body1: { fontSize: '1rem', fontWeight: 400, lineHeight: 1.5 },
body2: { fontSize: '0.875rem', fontWeight: 400, lineHeight: 1.43 },
button: { fontSize: '0.875rem', fontWeight: 600, textTransform: 'none' }, // disable ALL_CAPS
caption: { fontSize: '0.75rem', fontWeight: 400, lineHeight: 1.66 },
overline: { fontSize: '0.75rem', fontWeight: 400, textTransform: 'uppercase', letterSpacing: '0.08333em' },
},
});
Responsive typography
typography: {
h1: {
fontSize: '2rem',
[theme.breakpoints.up('md')]: { fontSize: '3rem' },
[theme.breakpoints.up('lg')]: { fontSize: '4rem' },
},
},
Spacing
The spacing scale is factor-based. Default unit is 8px.
const theme = createTheme({ spacing: 8 }); // default
theme.spacing(1) // '8px'
theme.spacing(2) // '16px'
theme.spacing(0.5) // '4px'
theme.spacing(1, 2) // '8px 16px'
theme.spacing(1, 2, 3, 4) // '8px 16px 24px 32px'
// Custom spacing function
const theme = createTheme({
spacing: (factor: number) => `${0.25 * factor}rem`,
});
Breakpoints
const theme = createTheme({
breakpoints: {
values: {
xs: 0,
sm: 600,
md: 900,
lg: 1200,
xl: 1536,
// Add custom breakpoints:
mobile: 0,
tablet: 640,
laptop: 1024,
desktop: 1200,
},
},
});
// Usage in sx
<Box sx={{ fontSize: { xs: '1rem', md: '1.5rem', lg: '2rem' } }} />
// Usage in styles
theme.breakpoints.up('md') // '@media (min-width:900px)'
theme.breakpoints.down('md') // '@media (max-width:899.95px)'
theme.breakpoints.between('sm', 'md') // '@media (min-width:600px) and (max-width:899.95px)'
theme.breakpoints.only('md') // '@media (min-width:900px) and (max-width:1199.95px)'
Shape, Shadows, Transitions, zIndex
const theme = createTheme({
shape: {
borderRadius: 8, // default: 4
},
// shadows[0] = 'none', shadows[1-24] = elevation levels
shadows: [
'none',
'0px 2px 1px -1px rgba(0,0,0,0.2),0px 1px 1px 0px rgba(0,0,0,0.14),0px 1px 3px 0px rgba(0,0,0,0.12)',
// ... (24 levels total — override individual elevations selectively)
...Array(23).fill('none'),
],
transitions: {
duration: {
shortest: 150,
shorter: 200,
short: 250,
standard: 300,
complex: 375,
enteringScreen: 225,
leavingScreen: 195,
},
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)',
sharp: 'cubic-bezier(0.4, 0, 0.6, 1)',
},
},
zIndex: {
mobileStepper: 1000,
fab: 1050,
speedDial: 1050,
appBar: 1100,
drawer: 1200,
modal: 1300,
snackbar: 1400,
tooltip: 1500,
},
});
ThemeProvider and CssBaseline
import { ThemeProvider, CssBaseline } from '@mui/material';
import { createTheme } from '@mui/material/styles';
const theme = createTheme({ ... });
function App() {
return (
<ThemeProvider theme={theme}>
{/* CssBaseline normalizes browser styles and sets body background */}
<CssBaseline />
<YourApp />
</ThemeProvider>
);
}
Dark Mode
Static dark mode
const darkTheme = createTheme({
palette: {
mode: 'dark',
// MUI auto-adjusts all palette colors for dark mode
// Override as needed:
primary: { main: '#90caf9' },
background: {
default: '#121212',
paper: '#1e1e1e',
},
},
});
Dynamic dark mode with ColorModeContext
import React, { createContext, useContext, useMemo, useState } from 'react';
import { ThemeProvider, createTheme } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline';
// Create context
const ColorModeContext = createContext({ toggleColorMode: () => {} });
export function useColorMode() {
return useContext(ColorModeContext);
}
export function ColorModeProvider({ children }: { children: React.ReactNode }) {
const [mode, setMode] = useState<'light' | 'dark'>('light');
const colorMode = useMemo(
() => ({
toggleColorMode: () =>
setMode((prev) => (prev === 'light' ? 'dark' : 'light')),
}),
[]
);
const theme = useMemo(
() =>
createTheme({
palette: { mode },
}),
[mode]
);
return (
<ColorModeContext.Provider value={colorMode}>
<ThemeProvider theme={theme}>
<CssBaseline />
{children}
</ThemeProvider>
</ColorModeContext.Provider>
);
}
// In a component:
function DarkModeToggle() {
const { toggleColorMode } = useColorMode();
return <IconButton onClick={toggleColorMode}><Brightness4Icon /></IconButton>;
}
System preference detection
import useMediaQuery from '@mui/material/useMediaQuery';
function App() {
const prefersDark = useMediaQuery('(prefers-color-scheme: dark)');
const [mode, setMode] = useState<'light' | 'dark'>(
prefersDark ? 'dark' : 'light'
);
const theme = useMemo(() => createTheme({ palette: { mode } }), [mode]);
// ...
}
Component-Level Overrides
styleOverrides
Override CSS for specific component slots:
const theme = createTheme({
components: {
MuiButton: {
styleOverrides: {
root: {
borderRadius: '8px',
textTransform: 'none',
fontWeight: 600,
},
contained: {
boxShadow: 'none',
'&:hover': { boxShadow: '0 2px 8px rgba(0,0,0,0.15)' },
},
sizeLarge: {
padding: '12px 24px',
fontSize: '1rem',
},
},
},
MuiTextField: {
styleOverrides: {
root: {
'& .MuiOutlinedInput-root': {
borderRadius: '8px',
},
},
},
},
MuiPaper: {
styleOverrides: {
root: ({ theme }) => ({
backgroundImage: 'none',
...(theme.palette.mode === 'dark' && {
backgroundColor: theme.palette.grey[900],
}),
}),
},
},
},
});
defaultProps
Set default prop values for all instances of a component:
const theme = createTheme({
components: {
MuiButton: {
defaultProps: {
variant: 'contained',
disableElevation: true,
},
},
MuiTextField: {
defaultProps: {
variant: 'outlined',
size: 'small',
fullWidth: true,
},
},
MuiCircularProgress: {
defaultProps: {
size: 24,
thickness: 4,
},
},
MuiLink: {
defaultProps: {
underline: 'hover',
},
},
},
});
Custom variants
Add new named variants to existing components:
const theme = createTheme({
components: {
MuiButton: {
variants: [
{
props: { variant: 'dashed' },
style: {
border: '2px dashed currentColor',
backgroundColor: 'transparent',
'&:hover': {
backgroundColor: 'rgba(0,0,0,0.04)',
},
},
},
{
props: { variant: 'gradient' },
style: ({ theme }) => ({
background: `linear-gradient(135deg, ${theme.palette.primary.main} 0%, ${theme.palette.secondary.main} 100%)`,
color: theme.palette.primary.contrastText,
border: 'none',
}),
},
],
},
},
});
Nested Themes and Theme Composition
// Merge two themes
import { deepmerge } from '@mui/utils';
const baseTheme = createTheme({ ... });
const extendedTheme = createTheme(deepmerge(baseTheme, {
typography: { h1: { fontSize: '3rem' } },
}));
// Nested ThemeProvider (child theme overrides parent locally)
function AdminPanel() {
const parentTheme = useTheme();
const adminTheme = createTheme(parentTheme, {
palette: {
primary: { main: '#dc2626' }, // red for admin
},
});
return (
<ThemeProvider theme={adminTheme}>
<AdminUI />
</ThemeProvider>
);
}
TypeScript Module Augmentation
Extend the theme types to support custom colors and variables:
// theme-augmentation.d.ts (or in your theme file)
import '@mui/material/styles';
import '@mui/material/Button';
declare module '@mui/material/styles' {
// Add custom palette colors
interface Palette {
neutral: Palette['primary'];
brand: Palette['primary'];
custom: {
gradientStart: string;
gradientEnd: string;
};
}
interface PaletteOptions {
neutral?: PaletteOptions['primary'];
brand?: PaletteOptions['primary'];
custom?: {
gradientStart?: string;
gradientEnd?: string;
};
}
// Add custom theme variables
interface Theme {
custom: {
navHeight: number;
sidebarWidth: number;
};
}
interface ThemeOptions {
custom?: {
navHeight?: number;
sidebarWidth?: number;
};
}
// Extend typography variants
interface TypographyVariants {
code: React.CSSProperties;
label: React.CSSProperties;
}
interface TypographyVariantsOptions {
code?: React.CSSProperties;
label?: React.CSSProperties;
}
}
// Allow usage of custom variants on Typography component
declare module '@mui/material/Typography' {
interface TypographyPropsVariantOverrides {
code: true;
label: true;
}
}
// Allow usage of custom palette colors on Button
declare module '@mui/material/Button' {
interface ButtonPropsColorOverrides {
neutral: true;
brand: true;
}
}
With augmentation in place, usage is fully typed:
const theme = createTheme({
palette: {
neutral: theme.palette.augmentColor({ color: { main: '#64748b' }, name: 'neutral' }),
custom: { gradientStart: '#667eea', gradientEnd: '#764ba2' },
},
custom: { navHeight: 64, sidebarWidth: 240 },
typography: {
code: { fontFamily: 'monospace', fontSize: '0.85rem', backgroundColor: 'rgba(0,0,0,0.06)' },
},
});
// Fully typed:
<Button color="neutral">Neutral Button</Button>
<Typography variant="code">const x = 1;</Typography>
const navH = theme.custom.navHeight; // typed as number
Design Token Patterns
Centralize all design decisions as named tokens, then reference them throughout the theme:
// design-tokens.ts
export const tokens = {
color: {
brand: {
50: '#eff6ff',
100: '#dbeafe',
500: '#3b82f6',
600: '#2563eb',
900: '#1e3a8a',
},
neutral: {
50: '#f8fafc',
100: '#f1f5f9',
500: '#64748b',
900: '#0f172a',
},
semantic: {
success: '#16a34a',
warning: '#d97706',
error: '#dc2626',
info: '#0284c7',
},
},
spacing: {
xs: 4,
sm: 8,
md: 16,
lg: 24,
xl: 32,
'2xl': 48,
'3xl': 64,
},
typography: {
fontFamily: {
sans: '"Inter", system-ui, sans-serif',
mono: '"JetBrains Mono", "Fira Code", monospace',
},
scale: {
xs: '0.75rem',
sm: '0.875rem',
base: '1rem',
lg: '1.125rem',
xl: '1.25rem',
'2xl': '1.5rem',
'3xl': '1.875rem',
'4xl': '2.25rem',
},
},
radius: {
sm: 4,
md: 8,
lg: 12,
xl: 16,
full: 9999,
},
} as const;
// theme.ts
import { createTheme } from '@mui/material/styles';
import { tokens } from './design-tokens';
export const theme = createTheme({
palette: {
primary: {
light: tokens.color.brand[100],
main: tokens.color.brand[500],
dark: tokens.color.brand[900],
},
success: { main: tokens.color.semantic.success },
warning: { main: tokens.color.semantic.warning },
error: { main: tokens.color.semantic.error },
info: { main: tokens.color.semantic.info },
},
typography: {
fontFamily: tokens.typography.fontFamily.sans,
h1: { fontSize: tokens.typography.scale['4xl'] },
h2: { fontSize: tokens.typography.scale['3xl'] },
h3: { fontSize: tokens.typography.scale['2xl'] },
body1: { fontSize: tokens.typography.scale.base },
body2: { fontSize: tokens.typography.scale.sm },
},
shape: { borderRadius: tokens.radius.md },
spacing: (factor: number) => `${tokens.spacing.xs * factor}px`,
});
Complete Theme Example
// theme/index.ts
import { createTheme, responsiveFontSizes } from '@mui/material/styles';
let theme = createTheme({
palette: {
mode: 'light',
primary: { main: '#2563eb' },
secondary: { main: '#7c3aed' },
error: { main: '#dc2626' },
warning: { main: '#d97706' },
info: { main: '#0284c7' },
success: { main: '#16a34a' },
background: { default: '#f8fafc', paper: '#ffffff' },
},
typography: {
fontFamily: '"Inter", "Roboto", sans-serif',
fontWeightBold: 700,
button: { textTransform: 'none', fontWeight: 600 },
},
shape: { borderRadius: 8 },
components: {
MuiButton: {
defaultProps: { disableElevation: true },
styleOverrides: {
root: { borderRadius: 8, padding: '8px 20px' },
},
},
MuiCard: {
defaultProps: { elevation: 0 },
styleOverrides: {
root: { border: '1px solid', borderColor: 'divider', borderRadius: 12 },
},
},
},
});
// Automatically scale typography for different screen sizes
theme = responsiveFontSizes(theme);
export default theme;
Extensible Theme Packaging
Ship a branded theme as an npm package for multi-app reuse.
// @your-org/mui-theme/src/index.ts
import { createTheme, type ThemeOptions } from '@mui/material/styles';
import { tokens } from './tokens';
import { componentOverrides } from './components';
// Base branded theme
export const brandedThemeOptions: ThemeOptions = {
palette: {
primary: { main: tokens.color.brand[500] },
secondary: { main: tokens.color.brand[700] },
},
typography: {
fontFamily: tokens.typography.fontFamily.sans,
button: { textTransform: 'none', fontWeight: 600 },
},
shape: { borderRadius: 8 },
components: componentOverrides,
};
// Create with optional per-app overrides
export function createBrandedTheme(...overrides: ThemeOptions[]) {
return createTheme(brandedThemeOptions, ...overrides);
}
// Pre-built themes
export const lightTheme = createBrandedTheme();
export const darkTheme = createBrandedTheme({
palette: { mode: 'dark' },
});
Consumer app layers overrides on top:
import { createBrandedTheme } from '@your-org/mui-theme';
const appTheme = createBrandedTheme({
components: {
MuiButton: {
styleOverrides: {
root: {
'&:hover': { transform: 'translateY(-2px)' },
},
},
},
},
});
Array styleOverrides merging: When extending, use arrays to preserve base styles:
root: [
brandedComponents.MuiButton.styleOverrides.root,
{ '&:hover': { transform: 'translateY(-2px)' } },
]
Theme as Rules Engine
Encode design decisions in the theme and enforce at compile time via module augmentation.
Restrict Allowed Variants
// Disable 'text' variant for Buttons in your design system
declare module '@mui/material/Button' {
interface ButtonPropsVariantOverrides {
text: false; // ← compile error if used
dashed: true; // ← new custom variant
gradient: true; // ← new custom variant
}
interface ButtonPropsColorOverrides {
inherit: false; // ← disable inherit
brand: true; // ← custom brand color
neutral: true; // ← custom neutral color
}
interface ButtonPropsSizeOverrides {
extraLarge: true; // ← custom size
}
}
Now <Button variant="text"> is a TypeScript error — design rules enforced at compile time.
Register Custom Components in Theme
// Register MuiStat in the theme like a built-in component
declare module '@mui/material/styles' {
interface Components {
MuiStat?: {
defaultProps?: Partial<StatProps>;
styleOverrides?: {
root?: any;
value?: any;
label?: any;
};
variants?: Array<{
props: Partial<StatProps>;
style: any;
}>;
};
}
}
// Now you can configure it globally via theme
const theme = createTheme({
components: {
MuiStat: {
defaultProps: { trend: 'flat' },
styleOverrides: {
root: ({ theme }) => ({
backgroundColor: theme.palette.background.paper,
'&:hover': { borderColor: theme.palette.primary.main },
}),
},
},
},
});
Function-Based Variant Props Matching
// Complex conditional variant matching
const theme = createTheme({
components: {
MuiButton: {
variants: [
{
// Function-based matching for complex rules
props: (props) =>
props.variant === 'dashed' && props.color !== 'secondary',
style: {
border: '2px dashed currentColor',
backgroundColor: 'transparent',
},
},
],
},
},
});
More from lobbi-docs/claude
vision-multimodal
Vision and multimodal capabilities for Claude including image analysis, PDF processing, and document understanding. Activate for image input, base64 encoding, multiple images, and visual analysis.
242design-system
Apply and manage the AI-powered design system with 50+ curated styles
126complex-reasoning
Multi-step reasoning patterns and frameworks for systematic problem solving. Activate for Chain-of-Thought, Tree-of-Thought, hypothesis-driven debugging, and structured analytical approaches that leverage extended thinking.
105gcp
Google Cloud Platform services including GKE, Cloud Run, Cloud Storage, BigQuery, and Pub/Sub. Activate for GCP infrastructure, Google Cloud deployment, and GCP integration.
73kanban
Kanban methodology including boards, WIP limits, flow metrics, and continuous delivery. Activate for Kanban boards, workflow visualization, and lean project management.
62debugging
Debugging techniques for Python, JavaScript, and distributed systems. Activate for troubleshooting, error analysis, log investigation, and performance debugging. Includes extended thinking integration for complex debugging scenarios.
59