CSS自定义属性与设计令牌联动,主题维护不再碎片化
做前端久了,多少都经历过这种场景:产品要加一套暗色主题,你打开样式文件,发现 `#3b82f6` 出现在按钮、链接、图标、边框里,一共 47 次;设计师说主色要调深一点,你改完发现漏了两处,线上出现“半旧半新”的蓝。更别提还有小程序、H5、后台三端,同一套品牌色各维护一份,改一次需求像做一次考古。
主题维护的碎片化,表面上看是“颜色写死了”,本质上是设计决策没有统一出口。而 CSS 自定义属性加上设计令牌,恰好能把这件事拉回正轨:令牌负责定义“是什么”,自定义属性负责让它在运行时“活得起来”。
设计令牌不是变量换皮
很多人以为把 `#3b82f6` 改成 `--color-primary` 就叫设计令牌了,其实还差得远。设计令牌的核心是“带语义的设计决策”,它至少应该分三层:
- 基础层:`--blue-500: #3b82f6`,只描述色值本身,不带业务含义;
- 语义层:`--color-action-primary: var(--blue-500)`,描述“主操作色”这个角色;
- 组件层:`--button-bg: var(--color-action-primary)`,把角色落到具体组件。
这样分层的好处是,换品牌色时只需要动基础层或语义层,组件层几乎不用改。更重要的是,语义层让“为什么这个按钮是蓝色”有了可追溯的答案,而不是靠代码考古。
用 CSS 自定义属性做运行时载体
设计令牌的源头可以是一份 JSON、YAML,甚至 Figma 变量,但最终落到浏览器里,CSS 自定义属性是最自然的载体。它有三个别的方案很难同时具备的能力:
第一,运行时可变。Sass 变量编译后就固定了,CSS 自定义属性却能在页面加载后动态修改,主题切换不需要重新构建。
第二,可继承、可作用域。变量会沿着 DOM 树继承,你可以在 `:root` 定义全局令牌,在某个卡片容器里覆盖局部主题,作用范围天然可控。
第三,能被 JS 读写。需要把主题色传给图表库、Canvas 或 WebGL 时,`getComputedStyle` 一取就行,不用在 JS 里再维护一份颜色常量。
三层令牌结构怎么落地
一个比较稳妥的落地方式,是让构建工具从令牌源文件生成 CSS 变量,而不是手写。比如令牌源文件里定义:
{
"color": {
"blue": { "500": { "value": "#3b82f6" } },
"action": { "primary": { "value": "{color.blue.500}" } }
}
}
构建后生成:
:root {
--color-blue-500: #3b82f6;
--color-action-primary: var(--color-blue-500);
}
[data-theme="dark"] {
--color-action-primary: var(--color-blue-400);
}
组件里只消费语义令牌:
.button {
background: var(--color-action-primary);
color: var(--color-text-on-action);
}
这样暗色主题不需要重写 `.button`,只需要在 `[data-theme="dark"]` 作用域下覆盖语义令牌。主题切换从“改样式”变成了“改变量”,维护面一下子收窄了。
主题切换与作用域设计
主题切换建议优先用 `data-theme` 属性,而不是给每个组件加类名。`html[data-theme="dark"]` 既能配合 JS 持久化用户选择,也能通过 `prefers-color-scheme` 做系统跟随:
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
--color-bg-canvas: var(--color-gray-950);
}
}
另外,别把所有令牌都挂在 `:root`。像“卡片内文字色”这种只在局部生效的令牌,定义在组件作用域里更合适,既减少全局污染,也避免不同组件之间的语义打架。
几个容易踩的坑
一是命名太随意。`--primary`、`--main-color`、`--brand` 混用,最后没人知道该用哪个。建议统一用“类别-角色-变体”的结构,比如 `--color-text-secondary`。
二是跳过语义层直接用基础色。组件里写 `var(--blue-500)`,暗色模式一来就抓瞎。基础层是原料,语义层才是菜。
三是忽略回退值。`var(--color-bg, #fff)` 在令牌加载失败或 SSR 首屏时能兜底,成本很低。
四是把令牌当万能药。间距、圆角、阴影、动效时长都值得令牌化,但不必为了令牌而令牌,否则变量文件会变成新的碎片源。
小结
CSS 自定义属性和设计令牌联动,真正解决的不是“少写几个颜色变量”,而是把设计决策收敛到一个可版本化、可构建、可运行时切换的出口。基础层管原料,语义层管角色,组件层管消费;主题切换只改变量,不碰组件样式。做到这一步,主题维护才算从“到处补丁”变成了“一处调整,全局生效”。
转载请注明出处,版权归原作者所有。
管理员
黑卡会员