MUI CSS 主题变量实战:theme.vars、Channel Token 与自定义 Token 的完整使用指南
Material UI(MUI)通过 cssVariables 选项将主题令牌序列化为全局 CSS 自定义属性,使浏览器开发者工具中可以直接看到每个样式值对应的主题 token。本文基于仓库中的官方使用文档 usage.md 展开,并结合 mui-system 的 cssVars 模块 源码,完整讲解启用方式、深浅色模式行为、theme.vars 与 var() 的用法、颜色通道令牌(channel tokens)、自定义令牌扩展以及 TypeScript 类型启用步骤。读完后你可以:在应用中开启 CSS 主题变量、安全地为深色模式写样式、创建半透明颜色,并按需扩展自己的主题 token。
启用 CSS 主题变量
使用方式非常直接:创建主题时传入 cssVariables: true,并用 ThemeProvider 包裹应用:
import { ThemeProvider, createTheme } from '@mui/material/styles';
const theme = createTheme({ cssVariables: true });
function App() {
return <ThemeProvider theme={theme}>{/* ...your app */}</ThemeProvider>;
}
渲染之后,你会在 HTML 文档的 :root 样式表中看到一组 CSS 变量。默认情况下这些变量是扁平化的,并以 --mui 作为前缀:
:root {
--mui-palette-primary-main: #1976d2;
--mui-palette-primary-light: #42a5f5;
--mui-palette-primary-dark: #1565c0;
--mui-palette-primary-contrastText: #fff;
/* ...other variables */
}
如果你此前使用的是实验性的
CssVarsProviderAPI,现在应替换为ThemeProvider。CssVarsProvider曾经提供的所有能力,如今都已由ThemeProvider承接。
源码层面:变量是如何注入页面的
从源码结构看,这条链路分为三步:
- 主题解析:createTheme.ts 中
cssVariables选项的签名是boolean | Pick<CssVarsThemeOptions, CssVarsConfigList>,默认值为false(第 72 行)。当传入true或配置对象时,主题会被追加colorSchemes、vars、generateStyleSheets等 CSS 变量基础设施;generateStyleSheets由 createCssVarsTheme.ts 赋值到主题输出上。 - 变量对象组装:Provider 在运行时计算
memoTheme,把vars: themeVars挂到主题上(themeVars来自generateThemeVars?.() || restThemeProp.vars),并把选定的 color scheme 一级合并进主题(createCssVarsProvider.js 第 155–191 行)。这就是后文theme.vars能镜像主题结构的原因。 - 样式表注入:Provider 最终渲染
<GlobalStyles styles={memoTheme.generateStyleSheets?.() || []} />(第 314–323 行),把:root(及深色方案选择器)下的变量声明写入全局样式表。遍历主题树、生成扁平变量与 channel token 的具体逻辑在 prepareCssVars.ts 中实现。
浅色与深色模式
当启用内置的深色 color scheme 且开启 cssVariables 时,浅色与深色的 CSS 变量会同时生成,并默认采用 CSS 媒体查询 prefers-color-scheme 方式切换。
这一方式的优点是:服务端渲染(SSR)下无需任何额外配置即可工作;缺点是用户无法手动切换模式,因为样式跟随浏览器媒体偏好。如果需要手动切换,需要把 colorSchemeSelector 改为 class 或 data 属性选择器,详见进阶配置文档中“手动切换深色模式”一节。
从源码可以印证这套机制:createCssVarsTheme.ts 第 13 行 中 colorSchemeSelector 的默认值是 [data-mui-color-scheme="%s"],而 Provider 会在色值方案变化时把该选择器解析为 class(如 .dark)或 data 属性(如 data-mui-color-scheme="dark")并写到 colorSchemeNode 上(createCssVarsProvider.js 第 196–237 行)。因此手动切换模式本质上是“改写根节点上的属性,让预先生成好的两套变量选择器命中不同的那份”。
应用深色样式
为深色模式定制样式时,请使用 theme.applyStyles() 函数(它在生成的样式表中输出对应 color scheme 选择器下的规则):
import Card from '@mui/material/Card';
<Card
sx={[
(theme) => ({
backgroundColor: theme.vars.palette.background.default,
}),
(theme) =>
theme.applyStyles('dark', {
backgroundColor: theme.vars.palette.grey[900],
}),
]}
/>;
注意:不要用
theme.palette.mode在浅色/深色样式之间做条件判断——这会产生 SSR 闪烁(flicker)问题。applyStyles()生成的是纯 CSS 选择器规则,样式在 JS 执行前就已生效,而palette.mode的判断发生在 JS 运行时,两者行为本质不同。applyStyles的类型定义见 createThemeFoundation.ts 第 433 行。
使用主题变量
启用 CSS 变量功能后,主题上新增 vars 节点。vars 对象镜像了可序列化主题的结构,其中每个值都指向一个 CSS 变量,实际渲染为 var(--mui-...) 形式。
theme.vars(推荐)
const Button = styled('button')(({ theme }) => ({
backgroundColor: theme.vars.palette.primary.main, // var(--mui-palette-primary-main)
color: theme.vars.palette.primary.contrastText, // var(--mui-palette-primary-contrastText)
}));
对于 TypeScript,类型默认不启用,需要按后文 TypeScript 一节 完成模块增强。
如果组件可能渲染在 Provider 之外(此时 theme.vars 不存在),请加上回退:
backgroundColor: (theme.vars || theme).palette.primary.main;
原生 CSS
当你无法访问主题对象(例如在纯 CSS 文件中),直接用 var() 引用全局变量:
/* external-scope.css */
.external-section {
background-color: var(--mui-palette-grey-50);
}
getCssVar:按字段名取值
主题还提供 getCssVar(field, ...fallbacks) 方法,免去手写前缀。其实现见 createGetCssVar.ts:将字段名拼成 var(--<prefix>-<field>),并对 fallback 值递归追加(仅当 fallback 不是原始颜色/数字值时才继续包一层 var())。类型声明位于 createThemeFoundation.ts 第 406 行,可用的字段名可通过 extendTheme.spec.ts 中的类型测试看到,例如 palette-primary-main、zIndex-appBar、shape-borderRadius 等扁平化字段。
颜色通道令牌(Channel Tokens)
启用 cssVariables 会自动生成 channel token,用于创建半透明颜色。这些 token 由颜色空间通道组成、不含 alpha 分量、以空格分隔,命名上以 Channel 结尾:
const theme = createTheme({ cssVariables: true });
console.log(theme.palette.primary.mainChannel); // '25 118 210'
// 该 token 由 `theme.colorSchemes.light.palette.primary.main` 派生。
利用 channel token 可以很方便地构造半透明色:
const theme = createTheme({
cssVariables: true,
components: {
MuiChip: {
styleOverrides: {
root: ({ theme }) => ({
variants: [
{
props: { variant: 'outlined', color: 'primary' },
style: {
backgroundColor: `rgba(${theme.vars.palette.primary.mainChannel} / 0.12)`,
},
},
],
}),
},
},
},
});
注意:分隔符不能用逗号(
,)。channel 颜色使用空格分隔通道,透明度部分以/连接(依据 CSS Color 4 规范的 modern 语法):`rgba(${theme.vars.palette.primary.mainChannel}, 0.12)`, // 🚫 不能工作 `rgba(${theme.vars.palette.primary.mainChannel} / 0.12)`, // ✅ 始终使用 `/`
这正是 createGetCssVar.ts 第 14 行 正则中专门识别 \d+ \d+ \d+(三个空格分隔的通道数字)的原因:当值为 channel token 时,工具链知道它是可组合的原始值而非完整颜色字符串。
添加自定义主题令牌
你可以往主题输入中添加任意 key-value 对,它们会作为 CSS 主题变量的一部分生成。注意自定义令牌建议放在对应 color scheme 的 palette 下,且值中可以直接引用其他变量:
const theme = createTheme({
cssVariables: true,
colorSchemes: {
light: {
palette: {
// 你可以在任意位置引用变量
gradient:
'linear-gradient(to left, var(--mui-palette-primary-main), var(--mui-palette-primary-dark))',
border: {
subtle: 'var(--mui-palette-neutral-200)',
},
},
},
dark: {
palette: {
gradient:
'linear-gradient(to left, var(--mui-palette-primary-light), var(--mui-palette-primary-main))',
border: {
subtle: 'var(--mui-palette-neutral-600)',
},
},
},
},
});
function App() {
return <ThemeProvider theme={theme}>...</ThemeProvider>;
}
随后可以从 theme.vars 对象访问这些变量:
const Divider = styled('hr')(({ theme }) => ({
height: 1,
border: '1px solid',
borderColor: theme.vars.palette.border.subtle,
backgroundColor: theme.vars.palette.gradient,
}));
或者用 var() 直接引用:
/* global.css */
.external-section {
background-color: var(--mui-palette-gradient);
}
如果你使用了自定义前缀,记得把上面示例中的默认
--mui替换为你的前缀。
从源码结构看,Provider 在组装 memoTheme 时会对选定 color scheme 做一级合并(第 171–190 行):palette 等对象型配置会被浅合并进主题。因此 light/dark 两套自定义令牌会同时进入 vars 输出,并在运行时按当前 color scheme 命中——这与内置 token 的行为完全一致。
TypeScript 类型启用
主题变量的类型默认不启用。你需要导入模块增强(module augmentation)来打开 theme.vars 的类型:
// 该 import 可以放在任何被 `tsconfig.json` 包含的文件中
import type {} from '@mui/material/themeCssVarsAugmentation';
import { styled } from '@mui/material/styles';
const StyledComponent = styled('button')(({ theme }) => ({
// ✅ typed-safe
color: theme.vars.palette.primary.main,
}));
增强模块对应仓库中的 packages/mui-material/src/themeCssVarsAugmentation/index.ts。
扩展 Palette 接口
当往 palette 中添加新令牌(如上文的 gradient、border.subtle)时,还需要增强 PaletteOptions 与 Palette 两个接口,createTheme 的入参和 theme.vars.palette 才能通过类型检查:
declare module '@mui/material/styles' {
interface PaletteOptions {
gradient: string;
border: {
subtle: string;
};
}
interface Palette {
gradient: string;
border: {
subtle: string;
};
}
}
相关文档与延伸阅读
- 启用与原理总览:CSS theme variables 概览
- 手动切换深色模式、自定义变量前缀、防 SSR 闪烁等进阶配置:configuration
- 原生 CSS 色彩方案相关用法:native-color
- 关键源码入口:createCssVarsProvider.js、prepareCssVars.ts、createCssVarsTheme.ts、createTheme.ts
如果你需要同时支持系统偏好和手动选择,建议下一步阅读进阶配置文档。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00