mui-theming
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseMUI Theming Skill
MUI主题技能
createTheme() API
createTheme() API
The function is the entry point for all MUI customization. It accepts a deeply partial theme object and merges it with the defaults.
createThemetsx
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: { ... },
});createThemetsx
import { createTheme } from '@mui/material/styles';
const theme = createTheme({
palette: { ... },
typography: { ... },
spacing: 8, // 基础间距单位(像素,默认值:8)
shape: { borderRadius: 8 },
breakpoints: { ... },
shadows: [...],
transitions: { ... },
zIndex: { ... },
});Palette
调色板
Standard palette colors
标准调色板颜色
tsx
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 , , and from if you omit them.
lightdarkcontrastTextmaintsx
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会自动从值生成它们。
lightdarkcontrastTextmainCustom palette colors with augmentColor
使用augmentColor自定义调色板颜色
tsx
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:
tsx
let theme = createTheme();
theme = createTheme(theme, {
palette: {
salmon: theme.palette.augmentColor({
color: { main: '#FF5733' },
name: 'salmon',
}),
},
});tsx
const theme = createTheme({
palette: {
// augmentColor会自动添加light/dark/contrastText
neutral: theme.palette.augmentColor({
color: { main: '#64748b' },
name: 'neutral',
}),
brand: theme.palette.augmentColor({
color: { main: '#6366f1', light: '#818cf8', dark: '#4f46e5' },
name: 'brand',
}),
},
});当augmentColor需要基础主题时,使用两步式createTheme调用:
tsx
let theme = createTheme();
theme = createTheme(theme, {
palette: {
salmon: theme.palette.augmentColor({
color: { main: '#FF5733' },
name: 'salmon',
}),
},
});Background and text
背景与文本
tsx
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)',
},
},tsx
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
排版
tsx
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' },
},
});tsx
const theme = createTheme({
typography: {
fontFamily: '"Inter", "Roboto", "Helvetica", "Arial", sans-serif',
fontSize: 14, // 基础字体大小(rem计算的根值)
htmlFontSize: 16, // <html>字体大小,用于rem计算
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' }, // 禁用全大写
caption: { fontSize: '0.75rem', fontWeight: 400, lineHeight: 1.66 },
overline: { fontSize: '0.75rem', fontWeight: 400, textTransform: 'uppercase', letterSpacing: '0.08333em' },
},
});Responsive typography
响应式排版
tsx
typography: {
h1: {
fontSize: '2rem',
[theme.breakpoints.up('md')]: { fontSize: '3rem' },
[theme.breakpoints.up('lg')]: { fontSize: '4rem' },
},
},tsx
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.
tsx
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`,
});间距系统基于因子计算,默认单位为8px。
tsx
const theme = createTheme({ spacing: 8 }); // 默认值
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'
// 自定义间距函数
const theme = createTheme({
spacing: (factor: number) => `${0.25 * factor}rem`,
});Breakpoints
断点
tsx
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)'tsx
const theme = createTheme({
breakpoints: {
values: {
xs: 0,
sm: 600,
md: 900,
lg: 1200,
xl: 1536,
// 添加自定义断点:
mobile: 0,
tablet: 640,
laptop: 1024,
desktop: 1200,
},
},
});
// 在sx属性中使用
<Box sx={{ fontSize: { xs: '1rem', md: '1.5rem', lg: '2rem' } }} />
// 在样式中使用
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
形状、阴影、过渡效果、zIndex
tsx
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,
},
});tsx
const theme = createTheme({
shape: {
borderRadius: 8, // 默认值:4
},
// shadows[0] = 'none',shadows[1-24] = 不同层级的阴影
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个层级——可选择性覆盖单个层级)
...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
ThemeProvider与CssBaseline
tsx
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>
);
}tsx
import { ThemeProvider, CssBaseline } from '@mui/material';
import { createTheme } from '@mui/material/styles';
const theme = createTheme({ ... });
function App() {
return (
<ThemeProvider theme={theme}>
{/* CssBaseline用于统一浏览器样式并设置body背景 */}
<CssBaseline />
<YourApp />
</ThemeProvider>
);
}Dark Mode
暗黑模式
Static dark mode
静态暗黑模式
tsx
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',
},
},
});tsx
const darkTheme = createTheme({
palette: {
mode: 'dark',
// MUI会自动为暗黑模式调整所有调色板颜色
// 按需覆盖:
primary: { main: '#90caf9' },
background: {
default: '#121212',
paper: '#1e1e1e',
},
},
});Dynamic dark mode with ColorModeContext
使用ColorModeContext实现动态暗黑模式
tsx
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>;
}tsx
import React, { createContext, useContext, useMemo, useState } from 'react';
import { ThemeProvider, createTheme } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline';
// 创建上下文
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>
);
}
// 在组件中使用:
function DarkModeToggle() {
const { toggleColorMode } = useColorMode();
return <IconButton onClick={toggleColorMode}><Brightness4Icon /></IconButton>;
}System preference detection
系统偏好检测
tsx
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]);
// ...
}tsx
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
styleOverrides
Override CSS for specific component slots:
tsx
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],
}),
}),
},
},
},
});覆盖特定组件插槽的CSS:
tsx
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
defaultProps
Set default prop values for all instances of a component:
tsx
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',
},
},
},
});为组件的所有实例设置默认属性值:
tsx
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:
tsx
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',
}),
},
],
},
},
});为现有组件添加新的命名变体:
tsx
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
嵌套主题与主题组合
tsx
// 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>
);
}tsx
// 合并两个主题
import { deepmerge } from '@mui/utils';
const baseTheme = createTheme({ ... });
const extendedTheme = createTheme(deepmerge(baseTheme, {
typography: { h1: { fontSize: '3rem' } },
}));
// 嵌套ThemeProvider(子主题会在局部覆盖父主题)
function AdminPanel() {
const parentTheme = useTheme();
const adminTheme = createTheme(parentTheme, {
palette: {
primary: { main: '#dc2626' }, // 管理员主题使用红色
},
});
return (
<ThemeProvider theme={adminTheme}>
<AdminUI />
</ThemeProvider>
);
}TypeScript Module Augmentation
TypeScript模块扩展
Extend the theme types to support custom colors and variables:
tsx
// 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:
tsx
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扩展主题类型以支持自定义颜色和变量:
tsx
// theme-augmentation.d.ts(或在你的主题文件中)
import '@mui/material/styles';
import '@mui/material/Button';
declare module '@mui/material/styles' {
// 添加自定义调色板颜色
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;
};
}
// 添加自定义主题变量
interface Theme {
custom: {
navHeight: number;
sidebarWidth: number;
};
}
interface ThemeOptions {
custom?: {
navHeight?: number;
sidebarWidth?: number;
};
}
// 扩展排版变体
interface TypographyVariants {
code: React.CSSProperties;
label: React.CSSProperties;
}
interface TypographyVariantsOptions {
code?: React.CSSProperties;
label?: React.CSSProperties;
}
}
// 允许在Typography组件上使用自定义变体
declare module '@mui/material/Typography' {
interface TypographyPropsVariantOverrides {
code: true;
label: true;
}
}
// 允许在Button上使用自定义调色板颜色
declare module '@mui/material/Button' {
interface ButtonPropsColorOverrides {
neutral: true;
brand: true;
}
}完成扩展后,使用时会获得完整的类型支持:
tsx
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)' },
},
});
// 完整类型支持:
<Button color="neutral">中性按钮</Button>
<Typography variant="code">const x = 1;</Typography>
const navH = theme.custom.navHeight; // 类型为numberDesign Token Patterns
设计令牌模式
Centralize all design decisions as named tokens, then reference them throughout the theme:
tsx
// 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`,
});将所有设计决策集中为命名令牌,然后在整个主题中引用它们:
tsx
// 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
完整主题示例
tsx
// 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;tsx
// 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 },
},
},
},
});
// 自动为不同屏幕尺寸缩放字体
theme = responsiveFontSizes(theme);
export default theme;Extensible Theme Packaging
可扩展主题打包
Ship a branded theme as an npm package for multi-app reuse.
ts
// @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:
ts
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:
ts
root: [
brandedComponents.MuiButton.styleOverrides.root,
{ '&:hover': { transform: 'translateY(-2px)' } },
]将品牌主题作为npm包发布,用于多应用复用。
ts
// @your-org/mui-theme/src/index.ts
import { createTheme, type ThemeOptions } from '@mui/material/styles';
import { tokens } from './tokens';
import { componentOverrides } from './components';
// 基础品牌主题
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,
};
// 创建主题时可传入可选的应用级覆盖配置
export function createBrandedTheme(...overrides: ThemeOptions[]) {
return createTheme(brandedThemeOptions, ...overrides);
}
// 预构建主题
export const lightTheme = createBrandedTheme();
export const darkTheme = createBrandedTheme({
palette: { mode: 'dark' },
});消费应用可在基础主题上添加覆盖配置:
ts
import { createBrandedTheme } from '@your-org/mui-theme';
const appTheme = createBrandedTheme({
components: {
MuiButton: {
styleOverrides: {
root: {
'&:hover': { transform: 'translateY(-2px)' },
},
},
},
},
});数组式styleOverrides合并:扩展时使用数组保留基础样式:
ts
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
限制允许的变体
ts
// 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 is a TypeScript error — design rules enforced at compile time.
<Button variant="text">ts
// 在你的设计系统中禁用Button的'text'变体
declare module '@mui/material/Button' {
interface ButtonPropsVariantOverrides {
text: false; // ← 使用时会触发编译错误
dashed: true; // ← 新的自定义变体
gradient: true; // ← 新的自定义变体
}
interface ButtonPropsColorOverrides {
inherit: false; // ← 禁用inherit颜色
brand: true; // ← 自定义品牌颜色
neutral: true; // ← 自定义中性颜色
}
interface ButtonPropsSizeOverrides {
extraLarge: true; // ← 自定义尺寸
}
}现在会触发TypeScript错误——设计规则在编译时得到强制执行。
<Button variant="text">Register Custom Components in Theme
在主题中注册自定义组件
ts
// 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 },
}),
},
},
},
});ts
// 将MuiStat像内置组件一样注册到主题中
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;
}>;
};
}
}
// 现在你可以通过主题全局配置它
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
基于函数的变体属性匹配
ts
// 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',
},
},
],
},
},
});ts
// 复杂条件变体匹配
const theme = createTheme({
components: {
MuiButton: {
variants: [
{
// 基于函数的匹配,支持复杂规则
props: (props) =>
props.variant === 'dashed' && props.color !== 'secondary',
style: {
border: '2px dashed currentColor',
backgroundColor: 'transparent',
},
},
],
},
},
});