CSS变量必须声明在:root中才生效,因其是自定义属性需依附选择器作用域;应按语义分组命名如--color-primary,避免硬编码值;暗色模式通过@media或[data-theme]覆盖:root变量实现;变量可嵌套引用但须防循环依赖。

变量必须声明在 :root 里,否则不生效
直接在 CSS 文件顶部写 --color-primary: #4F46E5 是无效的——浏览器会完全忽略它。CSS 变量是“自定义属性”,必须依附于一个选择器作用域。:root 是最合理的位置,它等价于 html 元素但优先级更高,且天然支持全局继承。
常见翻车点:
- 把变量塞进
@import的文件里,却忘了包一层:root { ... } - 误以为
@layer base能替代选择器,结果变量没被识别 - 用 JS 动态注入样式字符串时,漏掉
:root { }外壳,只写了变量行
正确写法:
:root {
--color-primary: #4F46E5;
--space-md: 1rem;
--radius-sm: 4px;
}按语义分组命名,别用具体值或平台色号
设计令牌的核心是“表达意图”,不是记录实现。看到 --blue-500 你无法判断它是按钮主色、链接色还是状态提示色;而 --color-primary 一眼可知用途,且换主题时只需改这一处。
立即学习“前端免费学习笔记(深入)”;
推荐结构:
- 统一前缀:所有颜色用
--color-,间距用--space-,圆角用--radius- - 分层语义:比如
--color-bg-default、--color-bg-inverted,比--bg-light/--bg-dark更易扩展 - 避免硬编码值:不要出现
--color-fuchsia-8这类依赖 Figma 命名的变量,它和设计系统脱钩
错误示例:--bg-main: #ffffff → 正确应为:--color-bg-surface: #ffffff
暗色模式切换靠覆盖,不是重写整套变量
别为 light/dark 各建一套独立 CSS 文件,也别用 JS 遍历 setProperty。最轻量、兼容性最好的方式,是在同一份 CSS 中用 [data-theme="dark"] 或 @media (prefers-color-scheme: dark) 覆盖 :root 里的值。
关键细节:
- 覆盖时仍用
:root或带属性的选择器,确保继承链完整 - 只覆盖变化项,比如
--color-text和--color-bg,其他如--space-md保持不变 - 如果用
prefers-color-scheme,记得 fallback 到:root定义默认值,否则无系统偏好时样式丢失
示例:
:root {
--color-text: #1a1a1a;
--color-bg: #ffffff;
}
<p>@media (prefers-color-scheme: dark) {
:root {
--color-text: #e0e0e0;
--color-bg: #121212;
}
}变量之间可嵌套引用,但要防循环依赖
var() 支持链式引用,比如用 --space-md 推导 --space-lg,这能减少魔法数字、提升一致性。但必须警惕隐式循环。
典型陷阱:
-
--space-lg: calc(var(--space-md) * 2)→ 没问题 -
--space-md: calc(var(--space-lg) / 2)→ 如果--space-lg尚未定义,计算结果为invalid,最终 fallback 到initial - 跨文件引用时,导入顺序错乱导致前置变量未声明
建议做法:所有基础变量(如 --space-xs)放在顶层 :root 块开头,衍生变量放后面;团队协作中用工具校验依赖图。
真正难的不是写多少变量,而是让每个变量都有明确的语义边界和稳定的使用契约。一旦开始混用平台色号、硬编码像素值或跨主题拆分文件,令牌系统就退化成另一套难以维护的 CSS 堆砌。


















