不能直接写--color-primary: #007bff,因为硬编码色值会导致多主题适配困难、全局替换易遗漏、设计与开发间tokens不一致;必须源自单一数据源(如tokens.json),经Style Dictionary等工具生成,确保可追溯、可验证、跨平台一致。

为什么不能直接写 --color-primary: #007bff?
因为设计系统不是“写一次就完事”,而是要支撑多主题、多平台、多人协作。硬编码值会让后续改色变成全局搜索替换,且无法保证 Figma → JSON → CSS 的一致性。真正能落地的 --color-primary 必须来自单一数据源(比如 tokens.json),否则设计师改一个值,前端可能漏掉三个文件。
常见错误现象:var(--spacing-4) 在组件里用了,但构建后发现 _variables.css 里压根没生成这个变量——说明 JSON 源里字段名拼错了,或者引用路径写成 "{spacing.base.4}" 却漏了 base 层级。
- 所有 Token 值必须是字符串或数字,不能是表达式(
calc()、rgba()等需提前算好) - 嵌套引用格式必须严格为
"{color.brand.primary}",不能带空格或多余引号 - Style Dictionary 默认不校验引用是否存在,得靠自定义 transform 函数做运行时检查
如何用 Style Dictionary 把 tokens.json 转成可靠的 CSS 变量
Style Dictionary 不是“一键生成”,关键在配置。默认 css/variables format 会把所有 Token 平铺进 :root,但企业级项目往往需要区分 light/dark 主题块、或按模块隔离(如 .chart-theme 下只挂 chart 相关变量)。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 在
config.json的transforms中启用attribute/cti(Category-Type-Item 命名规范),避免primaryBlue和bluePrimary混用 - 用
format自定义输出:例如写一个css/theme-blocksformat,让 dark 模式变量自动包裹在html.dark :root里 - 禁用默认的
name/cti/kebabtransform,改用name/cti/pascal+ 手动映射,防止font-size-h1被转成--font-size-h1(CSS 不支持连字符开头的变量名)
示例片段(config.json):
{
"source": ["tokens/**/*.json"],
"platforms": {
"css": {
"transformGroup": "css",
"buildPath": "src/assets/css/",
"files": [{
"destination": "tokens.css",
"format": "css/variables"
}]
}
}
}如何让 CSS 变量真正“活”起来:动态切换与作用域控制
生成 tokens.css 只是第一步。变量要生效,必须被正确注入且不被覆盖。Vben Admin 或 Ant Design 这类框架内部会调用 document.documentElement.style.setProperty(),但如果你自己手写 JS 切换主题,容易踩两个坑:
- 直接改
document.body上的变量——错!必须改document.documentElement(即<html>),否则:root作用域失效 - 用
innerHTML替换整个<style>标签——触发重排,且丢失已有的 inline style 绑定 - 没处理 CSS 选择器优先级:比如
.ant-btn { border-radius: var(--radius-m); }被 Ant Design 自带的.ant-btn { border-radius: 4px; }覆盖,因为后者 specificity 更高
解决办法很直接:
- 切换主题时只更新
document.documentElement.style,不要操作 DOM 树 - 给业务组件加前缀类(如
.my-app .ant-btn),确保你的var(--radius-m)规则优先级高于 UI 库默认样式 - 暗黑模式下,别只改颜色,同步更新
--color-scheme: dark,让原生控件(如<select>)也响应
为什么 calc(var(--spacing-2) * 2) 在 Safari 里不工作?
因为 CSS 自定义属性本身不参与计算,calc() 里的 var() 只是文本替换,浏览器不会解析其数值含义。Safari 直到 2026 年仍不支持在 calc() 中对未声明单位的变量做运算(如 --spacing-2: 8 而非 --spacing-2: 8px)。
工程上唯一可靠的做法:Token 构建阶段就完成计算。Style Dictionary 支持自定义 transform 函数,在读取 JSON 后、输出 CSS 前,把 "{spacing.2}" 替换成实际像素值,再乘以系数:
// 自定义 transform
"size/px-calc": {
"type": "value",
"matcher": function(token) {
return token.attributes.category === 'spacing' && token.value.includes('*');
},
"transformer": function(token) {
const [base, multiplier] = token.value.split('*').map(Number);
return `${base * multiplier}px`;
}
}这样生成的 CSS 就是 --spacing-4: 16px,而非留坑给运行时。
真正难的从来不是怎么写 var(--x),而是怎么让每个 --x 都有明确来源、可追溯、不可绕过、且在任何浏览器里都表现一致——这要求你从 JSON 结构设计开始,就拒绝“先写了再说”的侥幸心理。


















