Material UI v9 主题与设计令牌实战指南:从 createTheme 到 CSS 变量与 Windows 高对比模式
2026/9/8 17:58:29 网站建设 项目流程

Material UI v9 主题与设计令牌实战指南:从 createTheme 到 CSS 变量与 Windows 高对比模式

【免费下载链接】material-uiMaterial UI: Comprehensive React component library that implements Google's Material Design. Free forever.项目地址: https://gitcode.com/GitHub_Trending/ma/material-ui

适用版本说明:本文所述 API 面向 Material UI v9(版本范围>=9.0.0 <10.0.0)。该技能指南以 AGENTS.md 为纲,技术细节扎根于仓库docs/data/material/customization/(theming、palette、dark-mode、css-theme-variables、typography、spacing、shape)与packages/mui-material/src/styles/下的源码实现。若你正在使用其他大版本,请先核对本文 API 细节再行套用。

Material UI 的主题机制本质上是一份设计令牌对象:palette、typography、spacing、shape、breakpoints、zIndex、transitions 等,再加上可选的按组件默认值theme.components。要建立一套可切换明暗、可配合服务端渲染、甚至能适配 Windows 高对比模式的统一视觉体系,你需要掌握createTheme/ThemeProvider的基础用法、colorSchemescssVariables的取舍,以及自定义品牌令牌时对应的 TypeScript 类型增强方法。读完本文,你将能够在 Material UI v9 项目中独立搭建并扩展一套生产级主题,并理解其背后的源码原理。

一、主题的核心心智模型:一份对象 + 一次注入 + 三处读取

先用一句话概括 Material UI 主题化的工作方式:

一个 theme 就是一个包含设计令牌(design tokens)和可选组件级默认值的 JavaScript 对象;应用通常在启动阶段调用一次createTheme(或分步组合),把结果通过靠近根节点的<ThemeProvider>注入 React context,组件再通过useThemesxstyled读取令牌值。

createTheme为分水岭,可以把它拆成三层来理解:

  • 令牌层palettetypographyspacingshapebreakpointszIndextransitions,它们描述"设计上应该长什么样";
  • 行为层theme.componentsMui*组件键注入defaultPropsstyleOverridesvariants,让某个组件在全局范围内有一致的默认外观;
  • 注入层ThemeProvider把 theme 对象放进 React context,任何子孙组件都能拿到。

这套模型的源码起点位于 createTheme.ts:从源码结构看,当cssVariables未开启(默认false)时,它内部走createThemeNoVars路径,其注释明确写着"Behaves exactly as v5";而当开启 CSS 变量或colorSchemes时,则会进入带 vars 的处理管线。这与下文"CSS 变量是可选项"的说法一致——不使用 CSS 变量时,你的主题行为完全等价于经典的 v5 语义。

二、核心起步:createTheme + ThemeProvider + CssBaseline

基础三段式代码如下:

import { createTheme, ThemeProvider } from '@mui/material/styles'; const theme = createTheme({ palette: { primary: { main: '#1976d2' }, }, }); function App() { return ( <ThemeProvider theme={theme}> {/* 应用内容 */} </ThemeProvider> ); }

要点说明:

  1. 主题构建入口统一从@mui/material/styles导入createTheme(产出 Material UI 默认主题语义)。
  2. createTheme({ ... })构建主题对象后,用<ThemeProvider theme={theme}>包裹应用,使所有后代组件都能通过 context 拿到主题。
  3. 在 Provider 内部放置<CssBaseline />,以获得基准元素样式与正确的暗色背景行为(细节见 Dark mode)。

组件内读取主题的官方入口同样是@mui/material/styles下的useTheme()

import { useTheme } from '@mui/material/styles'; function MyComponent() { const theme = useTheme(); return <div style={{ color: theme.palette.primary.main }} />; }

useTheme之外,更推荐的方式是通过sxstyled的插值函数取令牌,例如sx={{ color: 'primary.main' }}styled('div')(({ theme }) => ({ color: theme.palette.primary.main }))——它们会自动把语义色名解析为主题值。

三、设计令牌全景图(Design Token Map)

下表来自该指南的核心表格,罗列了令牌存放的各个区域及其角色:

区域作用参考文档
palette语义色(primarysecondaryerror…)、文字、背景、分割线、action 色docs/data/material/customization/palette/
typographyfontFamilyfontSize、字体变体(h1~body2button…)docs/data/material/customization/typography/
spacingtheme.spacing(n)间距标尺(默认每单位 8px)docs/data/material/customization/spacing/
shapeborderRadius(默认 4);新增额外圆角需做 TypeScript 类型增强docs/data/material/customization/shape/
breakpointssx/ 媒体查询使用的响应式键docs/data/material/customization/breakpoints/
zIndex层级令牌docs/data/material/customization/z-index/
transitions时长 / 缓动函数辅助docs/data/material/customization/transitions/
componentsMui*键配置defaultPropsstyleOverridesvariantsdocs/data/material/customization/theme-components/

完整的默认值明细可参考仓库中docs/data/material/customization/default-theme/下的默认主题文档(Default theme explorer),它逐项列出每个令牌在生产主题里的默认值,适合作为调试"这个值为什么是这个颜色"的对照表。

从源码结构看,令牌在生产主题中并非全部原样暴露:例如 createTheme.ts 会通过createMixinscreateTypographycreatePalette等工厂函数把简写输入(如只给palette.primary.main)展开成包含light/dark/contrastText等派生字段的完整结构,这也是"只给main也能跑"的底层原因。

四、Palette 速查:三个高频事实

使用 palette 时应记住以下三点:

  1. 每个 palette 颜色通常包含mainlightdarkcontrastText四个字段。多数情况下只需提供maincreateTheme会根据亮度算法自动推导其余字段与对比度文字色。这条规则同样适用于primarysecondary这类语义色键之外的自定义键(status.danger之类)。
  2. 构建调色板时优先复用@mui/material/colors,例如import { purple } from '@mui/material/colors'后取purple[500],以获得符合 Material Design 规范的现成色阶,而不是手写难以对齐的十六进制色。
  3. palette.mode: 'dark'会把整套主题强制切换成暗色调色板。注意:如果使用全自定义调色板并配合暗色模式,务必保证自定义色值与当前 mode 的语义相符——例如暗色下背景应使用深色系,否则可能出现深底浅字之外的错乱观感。详见 Dark mode。

五、colorSchemes vs palette-only 暗色:系统级明暗方案的正确姿势

如果你需要"跟随系统偏好、跨标签页同步、切换时可关闭过渡动画、兼容 SSR"这类完整的明暗切换体验,指南明确建议使用colorSchemes而非旧的、能力较窄的"仅设palette.mode"方案。

import { createTheme } from '@mui/material/styles'; // 内置 light + dark 两套 scheme const theme = createTheme({ colorSchemes: { dark: true }, });

几个必须牢记的判定规则:

  • colorSchemespalette同时存在时,palette优先。避免无意中覆盖。这一点在源码中有直接体现:createTheme 内部会把传入的palette通过attachColorScheme逻辑合并进对应 scheme(createTheme.ts 附近的attachColorScheme会以palette为准重建 scheme 的palette)。
  • defaultColorScheme默认取palette?.mode:从 createTheme.ts 可见,未显式声明palette时默认colorSchemes = { light: true },默认 scheme 在未指定palette.mode时回落到'light'。也就是说,palette.mode: 'dark'依然可以表达"默认就是暗色",只是它不再被当作独立的运行时切换机制。
  • useColorScheme读取 / 更新当前 mode 以实现切换。注意首次渲染时mode可能是undefined(系统偏好尚未确定 / SSR 尚未水合),代码需显式处理该分支,避免水合(hydration)不一致。
  • ThemeProvider上支持storageManagerdisableTransitionOnChangenoSsr等属性,用于定制持久化存储、切换 scheme 时关闭过渡动画,以及控制 SSR 期间的颜色方案行为。这些细节都收录在 Dark mode 文档中。

如果你已经用 v5 时代的"静态palette.mode+ 两个 theme 手动切换"方案工作了很久,迁移到colorSchemes的核心收益在于:系统偏好监听、多标签页同步、以及 scheme 切换时避免整页重挂载的体验,都可以交给框架层处理。

六、开启 CSS 主题变量:cssVariables: true 与 theme.vars

当需要"更清晰的调试、暗色局部区域的更少主题嵌套、以及切换时更少的 JS 计算"时,可以在createTheme中开启cssVariables: true

import { createTheme } from '@mui/material/styles'; const theme = createTheme({ cssVariables: true, colorSchemes: { light: true, dark: true }, });

开启后,组件的样式将使用var(--mui-...)形式的 CSS 变量;对应地,在样式回调中应优先使用theme.vars(它把 palette / typography 等令牌镜像成指向 CSS 变量的引用),而不是直接读theme.palette.*的静态值。用法细节见 Usage。

关于cssVariables的选项形态,从 createTheme.ts 的类型定义可以看出它既可以传boolean,也可以传Pick<CssVarsThemeOptions, CssVarsConfigList>对象做更细粒度的配置(例如通过colorSchemeSelector控制选择器形态)。

四个高频注意事项:

  1. 不要向createTheme传自定义的vars。该键为 CSS 变量功能保留,由框架自动生成,手动传入会与自动生成逻辑冲突。
  2. CSS 变量下的暗色专属样式,用theme.applyStyles('dark', { ... }),而不要用基于theme.palette.mode的分支写法——后一种方式在方案切换时容易造成闪烁。官方在 Usage 与 Configuration 中均有明确警告。
  3. 防首帧闪烁脚本的位置InitColorSchemeScript必须放在任何渲染内容之前,以阻止初始 color-scheme 的闪烁。具体到路由框架:
    • App Router:放在app/layout.tsx<body>内、{children}之前;
    • Pages Router:放在_document.tsx中、<Main />之前。
  4. 权衡取舍:开启后 HTML 体积会变大(同时输出明暗两套方案的变量,可能影响 FCP);收益是切换 scheme 时更少的 JS 工作量与更好的 SSR 暗色体验。综述见 Overview。

此外,老版本的CssVarsProvider已被具备同等能力的ThemeProvider取代——v9 中统一使用ThemeProvider(配合cssVariablescolorSchemes选项)即可。

关于theme.vars的类型theme.vars的类型默认并未启用。若要在 TypeScript 中使用,需要参照 Usage 中的 TypeScript 章节开启相关类型声明(见 reference.md)。当组件可能运行在ThemeProvider之外时,推荐使用兼容两种形态的兜底写法:

backgroundColor: (theme.vars || theme).palette.primary.main;

这段代码同时覆盖"开启了 cssVariables(有theme.vars)"与"未开启(只有theme.palette)"两种运行时,是 reference.md 提供的官方推荐 fallback 写法。

七、Typography 与 Spacing:两个最容易踩坑的度量体系

排版

  • Material UI 的排版使用rem单位,默认根字号语义与设计规范见 Typography 文档。
  • 可通过调整typography.fontSize(修改基准字号)或逐个字体变体(h1~body2button等)的fontSize来覆盖。
  • 若希望排版随断点整体缩放,用responsiveFontSizes(theme)包装一次即可(它位于@mui/material/styles,与enhanceHighContrast属于同一类"主题增强器"模式):
import { createTheme, responsiveFontSizes } from '@mui/material/styles'; let theme = createTheme(); theme = responsiveFontSizes(theme);

间距

  • theme.spacing(n)遵循配置好的间距标尺,默认每单位 8px(spacing(2)即 16px);sx里的间距简写(如p: 2gap: 1)走同一套系统,因此sxtheme.spacing语义天然一致。
  • 数组形式的spacing配置存在表达力限制(对负数、小数、'auto'支持不完整)。需要完整表达力时,在主题中把spacing配成函数形式,而不是数组。

八、主题的组合与合并:分步 createTheme 与 deepmerge

现实项目中经常出现"某个令牌要由另一个令牌推导而来"的需求。官方推荐的分步构建法:

import { createTheme } from '@mui/material/styles'; // 第一步:用基础选项产出完整主题 const baseTheme = createTheme({ palette: { primary: { main: '#1976d2' } }, }); // 第二步:把第一步的成果当作输入,再派生新主题 const derivedTheme = createTheme(baseTheme, { typography: { h1: { color: baseTheme.palette.primary.main }, }, });

这等价于官方文档 "Using theme options to define other options"(主题组合)章节的推荐实践。要点在 Theming 文档 中亦有展开。

两条硬性规则:

  1. 不要把多个参数当作"自动深合并"来依赖——createTheme只会正式处理第一个参数(对应官方 "createTheme(options, ...args)" 的说明)。也就是说,createTheme(a, b)里的b不会被规范地深合并进结果。
  2. 自行完成深合并后再一次性传入。可以借助@mui/utilsdeepmerge
import { deepmerge } from '@mui/utils'; import { createTheme } from '@mui/material/styles'; const theme = createTheme(deepmerge(baseOptions, partialOverrides));

这种"显式深合并 + 单对象传入"的写法能保证前向兼容,也不会对参数处理次序产生隐式依赖。

九、嵌套 ThemeProvider:局部覆盖外层主题

ThemeProvider支持嵌套,内层 Provider 覆盖外层

import { createTheme, ThemeProvider } from '@mui/material/styles'; const outer = createTheme({ palette: { primary: { main: '#1976d2' } } }); const inner = createTheme({ palette: { primary: { main: '#9c27b0' } } }); <ThemeProvider theme={outer}> <App /> <ThemeProvider theme={inner}> <IsolatedSection /> {/* 此处用紫色 primary */} </ThemeProvider> </ThemeProvider>

只有当你有意在父主题基础上扩展时,才使用函数式写法theme={(outerTheme) => createTheme({ ...outerTheme, ...overrides })}——此时outerTheme是已完成解析的完整主题对象,{ ...outerTheme }会把外层全部令牌带进新主题。注意函数式写法要求拿到的是"已解析主题"而非"原始 options",所以它适合在 Provider 层级做基于父主题的增量定制,而不是用来替代createTheme的一次性组合。

十、自定义品牌设计令牌与 TypeScript 类型增强

当你需要承载品牌专属的设计键时(例如status.danger),分两步走:

第 1 步:在createTheme中挂载自定义键:

import { createTheme } from '@mui/material/styles'; const theme = createTheme({ status: { danger: '#e53e3e' }, palette: { primary: { main: '#1976d2' }, // 需要新增调色板字段时,参照 palette 文档的增强模式 }, });

第 2 步:同时增强ThemeThemeOptions两个接口(完整模板见 reference.md):

declare module '@mui/material/styles' { interface Theme { status: { danger: string }; } interface ThemeOptions { status?: { danger?: string }; } }

必须同时增强两者,是因为Theme描述的是"已解析主题"的形态,而ThemeOptions描述的是createTheme入参的形态——只增强其一,要么是入参时类型报错,要么是消费时读不到类型。

三条相关的扩展守则:

  • 扩展shape:新增圆角键时必须同时增强ShapeShapeOptions两个接口(参考 reference.md 中的说明),这是shape类型体系的特殊要求。
  • 扩展 palette:往 palette 加业务字段时,遵循 palette 文档中的 TypeScript 增强模式,同样要保证Palette/PaletteOptions两侧类型一致。
  • 严禁把theme.vars用作自定义属性名:它是 CSS 变量支持功能的私有字段(AGENTS.md 与 reference.md 均明确警示),占用它会与框架自动生成的 vars 结构冲突。

十一、Windows 高对比模式:enhanceHighContrast 主题增强器

enhanceHighContrast是 Material UI v9 提供的一个主题增强器(与responsiveFontSizes属于同一设计模式),作用是为 MUI 组件追加@media (forced-colors: active)覆盖,从而在 Windows 高对比 / 强制颜色(Forced Colors)模式下保持良好的可读性。

最小用法:

import { createTheme, enhanceHighContrast } from '@mui/material/styles'; const theme = enhanceHighContrast(createTheme());

三个关键事实(均有源码佐证,实现见 enhanceHighContrast.ts):

  1. 它接收一个已完全创建的 theme,返回增强后的副本——务必在createTheme之后调用,绝不能包在createTheme内部
  2. 默认使用 CSS 系统颜色关键字HighlightHighlightTextButtonBorder等),而非项目色板中的具体色值。默认 token 全集如下表(摘自 enhanceHighContrast.ts 的defaultHcTokens):
Token默认系统色关键字用途
disabledGrayText禁用元素颜色
errorActiveText错误状态色
selectedBackgroundSelectedItem选中项背景
selectedTextSelectedItemText选中项文字
activeBackgroundHighlight激活/选中(toggled)控件背景
activeTextHighlightText激活/选中控件文字
buttonBorderButtonBorder交互控件边框
buttonTextButtonText按钮文字/图标
canvasCanvas页面/画布背景
  1. 需要对齐品牌色时可传第二个参数覆盖个别 token
const theme = enhanceHighContrast(createTheme(), { activeBackground: 'SelectedItem', // 例:切换/激活控件背景 activeText: 'SelectedItemText', });

约束:token 值只能传 CSS 系统颜色关键字——因为只有这些关键字是浏览器能保证与配对的 token 稳定保持对比度的值(CSS Color 4 规范定义的 system colors)。传入项目十六进制色既可能破坏高对比语义,也可能因用户系统主题不同而失效。

从源码还可以看到两个值得注意的实现细节(enhanceHighContrast.ts):

  • 函数内部把所有 HCM 覆盖合并进theme.componentsstyleOverrides,覆盖组件涵盖MuiAccordionSummaryMuiAutocompleteMuiCheckboxMuiFilledInputMuiFormControlLabelMuiFormHelperTextMuiFormLabelMuiInputMuiLinearProgressMuiInputBaseMuiMenuItemMuiListItemIconMuiListItemButtonMuiNativeSelectMuiOutlinedInputMuiRadioMuiSliderMuiSwitchMuiButtonBaseMuiTooltipMuiToggleButton等常用表单与导航组件;
  • 它刻意用数组形式合并styleOverrides,使每个条目作为独立 CSS 规则输出,让浏览器级联(而非 JS 对象合并)来裁决优先级——这对forced-colors覆盖与原主题样式共存是必要的。

由于对MuiSliderMuiSwitch这类"track / thumb 子元素拿不到 disabled class"的组件,源码改用ownerState回调判断禁用态(enhanceHighContrast.ts),这也提醒你:当自定义 styleOverrides 需要感知禁用态时,优先依赖ownerState而不是 DOM class。相关测试见 enhanceHighContrast.test.ts,可当作验证函数行为与回归边界的参考。

十二、进一步阅读:仓库内一手资料索引

主题仓库内一手资料
Theming 概览与 API(组合、嵌套、自定义变量)theming.md
暗色模式与切换(colorSchemes、storage、SSR)dark-mode.md
CSS 主题变量综述overview.md
CSS 主题变量用法(theme.vars、applyStyles)usage.md
CSS 主题变量配置(InitColorSchemeScript、SSR 防闪烁)configuration.md
Windows 高对比模式 token 全集与示例docs/data/material/customization/palette/(Palette 文档 High Contrast 章节)
色板 / 品牌色阶工具docs/data/material/customization/color/
TypeScript 主题定制(Theme / ThemeOptions 增强)theming.md 与 reference.md

最后强调一遍最容易犯的三个错误,对照自查:一是把palettecolorSchemes混用导致误覆盖(前者优先);二是直接读写保留字段vars;三是在 cssVariables 场景下用theme.palette.mode分支编写暗色样式而非theme.applyStyles。避开这三点,你的主题体系就能同时稳定服务于明暗切换、SSR 渲染与无障碍高对比场景。

【免费下载链接】material-uiMaterial UI: Comprehensive React component library that implements Google's Material Design. Free forever.项目地址: https://gitcode.com/GitHub_Trending/ma/material-ui

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询