CSS 变量与设计令牌:让主题切换变简单
把颜色、圆角、间距抽成 CSS 自定义属性,是前端工程里性价比最高的一次重构。它不需要构建工具,浏览器原生支持,而且可以在运行时改变。
变量是继承的
--name 定义在哪个元素上,就作用于该元素及其子树。所以把令牌放在 :root 上就是全局变量,放在某个容器上就是局部变量。
:root {
--brand: #2b5cff;
--bg: #ffffff;
--ink: #101828;
--line: #e6e9f0;
--radius: 12px;
}
.card {
background: var(--bg);
color: var(--ink);
border: 1px solid var(--line);
border-radius: var(--radius);
}
主题切换的两种方式
第一种是给 <html> 加 data-theme 属性,用属性选择器覆盖变量:
[data-theme="dark"] {
--bg: #0f1419;
--ink: #e6e9ef;
--line: #232a35;
}
第二种是用 prefers-color-scheme 跟随系统。两者可以组合:默认跟随系统,用户手动切换后写 data-theme 覆盖。
const saved = localStorage.getItem('theme');
if (saved) document.documentElement.dataset.theme = saved;
注意:主题状态存 localStorage 是「界面偏好」,不属于用户数据,刷新后需要立刻恢复,所以要在 <head> 里尽早执行,避免白屏闪烁(FOUC)。
变量也能参与计算
自定义属性本身是字符串,但配合 calc() 可以写相对值:
:root { --gap: 8px; }
.grid { gap: calc(var(--gap) * 2); }
需要注意,calc() 里的变量必须带单位。如果希望变量是纯数字再乘单位,要用 calc(var(--n) * 1px) 这种写法,或者用新的 @property 声明类型。
什么时候不该用变量
不是所有值都值得抽成变量。只用一次的颜色、一次性的定位偏移,直接写在规则里更清晰。判断标准是:这个值是否需要在多处保持一致,或者需要被运行时改变。两个条件都不满足时,抽象只会增加阅读成本。