CSS 变量:让设计系统真正落地的那把钥匙

渐变抽象图

很多团队都号称有「设计系统」,但改一次主色,还是得满仓库搜索替换。这通常不是设计系统的问题,而是变量分层没做对。

一、问题:改了 20 个文件还没改完

先看一段很常见的代码:

.button-primary {
  background: #5b5bd6;
  border: 1px solid #4a4ab8;
}

.link {
  color: #5b5bd6;
}

.badge {
  background: rgba(91, 91, 214, .12);
  color: #5b5bd6;
}

这段代码里,同一个颜色以三种形式出现。一旦品牌色微调,你需要全部重找一遍,而且极容易漏掉 rgba 那一种。

二、三层变量结构

我的做法是把变量分成三层,各司其职:

/* 第一层:原始调色板 —— 只描述颜色本身,不带含义 */
:root {
  --indigo-500: #5b5bd6;
  --indigo-600: #4a4ab8;
  --coral-500:  #e0607e;
  --gray-50:    #f7f7fb;
  --gray-900:   #171a2b;
}

/* 第二层:语义变量 —— 描述用途,业务代码只用这一层 */
:root {
  --color-accent:       var(--indigo-500);
  --color-accent-hover: var(--indigo-600);
  --color-bg:           var(--gray-50);
  --color-text:         var(--gray-900);
}

/* 第三层:组件变量 —— 只在需要局部定制时使用 */
.card {
  --card-pad: 1.35rem;
  padding: var(--card-pad);
}

分层的价值在于:换色只改第一层,改语义只改第二层,业务代码永远只碰第三层或第二层。层级清晰之后,一次主题调整从「半小时」变成「三十秒」。

三、语义化命名,而不是描述性命名

这是最常见的误区。对比一下:

  • ❌ --blue-button:描述的是颜色,一旦按钮变成绿色,这个变量名就成了谎言。
  • ✅ --color-primary-action:描述的是用途,颜色怎么换名字都成立。
变量名应该回答「它用来做什么」,而不是「它看起来像什么」。

四、暗色模式其实就是覆盖一层

因为业务代码只引用语义变量,暗色模式要做的事情就变得极其简单——只需要覆盖第二层:

html[data-theme="dark"] {
  --color-bg:   #0e1017;
  --color-text: #e9ecf5;
  --color-accent: #8b8bfa;
}

整个站点就完成换肤了,一行组件样式都不用改。这也是本站正在用的方案,你点右上角的图标就能看到效果。

五、几个容易踩的坑

  1. 别把变量当成万能胶。间距、字号这类需要参与计算的数值,变量化收益有限,反而降低可读性。
  2. 注意过渡闪烁。暗色模式切换时如果颜色是硬切的,观感会很廉价,给 body 加个 transition: background .3s 会好很多。
  3. 避免在变量里做复杂计算。浏览器无法优化的表达式放在变量里,会拖慢重绘。

CSS 变量不是新语法糖,它是一种让「设计意图」可被代码表达的机制。用好了,设计系统才真的能落地。