CSS三层Token:比Sass变量更强的样式管理方案

更新日期: 2026-04-21 阅读: 23 标签: 变量

最近看到一个设计系统,用原生CSS的三层Token管理样式。看完之后我的判断是:在抽象能力上,这套系统比Sass变量强。

听起来有点反直觉。预处理器应该更强才对,有更多语法糖,更多编程能力。但三层Token的分层设计,让主题切换和组件复用变干净了,这是Sass做不到的。


Sass变量写久了会乱

项目小的时候,几个变量够用。$primary-color、$text-color,清清楚楚。项目一大,变量开始失控。$primary-dark、$primary-light、$text-primary、$text-secondary……谁也记不住哪个该用在哪儿。

加暗黑模式更烦。得新建一套变量:$dark-primary、$dark-text……然后代码里到处@if $theme == dark。改完暗黑模式,下次加护眼模式,又来一遍。

还有一个问题:语义和数值绑死了。$primary-color: #3B82F6,“主色”和“蓝色”绑在一起。想换颜色,得改变量值。但“主色”在不同组件里可能不一样。按钮的主色、链接的主色……拆着拆着变量越来越多。

这些问题的根源:Sass变量只有一层,没有分层抽象。


三层Token怎么分层

三层Token把变量分成三层,每层有明确职责。

Primitives:原始层(--p-)

这一层只定义“有什么”,不管“用在哪儿”。

/* 颜色 */
--p-color-blue-500: #3B82F6;
--p-color-blue-600: #2563EB;
--p-color-gray-100: #F3F4F6;
--p-color-gray-900: #111827;

/* 间距 */
--p-spacing-1: 0.25rem;
--p-spacing-2: 0.5rem;
--p-spacing-4: 1rem;

/* 圆角 */
--p-radius-sm: 0.25rem;
--p-radius-md: 0.5rem;

这些变量不带语义。--p-color-blue-500只是一个蓝色,不代表“主色”或“链接色”。语义在下一层定义。

Semantic:语义层(--s-)

给Primitives赋予含义。这一层决定“用在哪儿”。

/* 语义定义 */
--s-color-primary: var(--p-color-blue-500);
--s-color-primary-hover: var(--p-color-blue-600);
--s-color-text: var(--p-color-gray-900);
--s-color-text-muted: var(--p-color-gray-100);
--s-spacing-default: var(--p-spacing-4);

组件只读Semantic,不直接读Primitives。这样的好处是:换主题时只改Semantic,不动组件代码。

Component:组件层(--c-)

组件专用的覆盖钩子。

.button {
  --btn-bg: var(--c-button-bg, var(--s-color-primary));
  --btn-radius: var(--c-button-radius, var(--p-radius-md));
  background: var(--btn-bg);
  border-radius: var(--btn-radius);
}

var(--c-button-bg, var(--s-color-primary))的意思是:如果定义了组件级覆盖就用它,否则回退到语义默认值。

某个按钮想用绿色?只改Component层:

.button--success {
  --c-button-bg: var(--p-color-green-500);
}

不动Semantic,不影响其他按钮。


为什么比Sass变量强

主题切换只改一层

加暗黑模式,只改Semantic:

:root {
  --s-color-text: light-dark(
    var(--p-color-warm-text),  /* 亮色模式 */
    var(--p-color-cool-text)   /* 暗黑模式 */
  );
  --s-color-bg: light-dark(
    var(--p-color-warm-bg),
    var(--p-color-cool-bg)
  );
}

Primitives不动,Component不动,只改Semantic的映射关系。Sass要改主题?新建整套变量,然后代码里到处判断。

切换机制也很简单:

html { color-scheme: light dark; }        /* 跟随系统 */
html.light { color-scheme: light only; }  /* 强制亮色 */
html.dark { color-scheme: dark only; }    /* 强制暗色 */

JS只负责切换.light或.dark类,不用改任何CSS变量。

配合原生函数

light-dark()是CSS原生函数,一个Token搞定两种主题。Sass没这个能力,得自己写mixin:

// Sass的做法
@mixin themed {
  @content;
  .dark & {
    @content($theme: dark);
  }
}

然后每个用到颜色的地方都包一层。三层Token直接在变量定义里解决,组件代码不用动。

运行时能力

CSS变量是运行时的,JS可以动态改:

// 用户选了自定义主题色
document.documentElement.style.setProperty(
  '--s-color-primary',
  userPickedColor
);

Sass变量编译完就固定了。用户运行时切换主题?得重新加载CSS或用JS替换类名,没有CSS变量干净。

命名不失控

--p-、--s-、--c-,三层命名有规则。一看变量名就知道:

  • --p-color-blue-500:原始蓝色,定义色板时用

  • --s-color-primary:主色,组件里用

  • --c-button-bg:按钮背景覆盖,特定按钮用

Sass变量只有一层,命名全靠自觉。项目小还好,项目大了各人命名习惯不一样,变量名越来越乱。


什么时候该用三层Token

三层Token确实能把主题切换做得很干净,但这种干净是用前期的约束换来的。

如果你的业务只是一些三五页面的轻量项目,或者只需要偶尔切个暗黑模式,那强推这套分层就是过度设计。更别说如果是重度依赖Sass的老项目,光剥离各种mixin和function就能脱层皮。对于这种轻量或历史负担重的场景,老老实实用单层变量配合light-dark()函数兜底就行:

:root {
  --color-text: light-dark(#111827, #F9FAFB);
  --color-bg: light-dark(#FFFFFF, #111827);
}

这套分层架构真正能发挥威力的场景,是那些变量规模快要失控的中大型前端工程,或是需要跨产品集成的正式设计系统。在这些场景里,团队的痛点早就不再是多写几行代码,而是“新人一上手,根本不敢改以前的颜色”。这个时候,靠着一套严谨的前缀规范(--p-、--s-、--c-)和强隔离的语义映射,你的样式架构才能真正立得稳。


总结

原生CSS的Token系统,强在分层抽象。

Sass变量只有一层,变量多了命名失控,主题切换到处改。三层Token把变量分成Primitives、Semantic、Component,每层职责清晰。配合light-dark()和运行时能力,主题切换和组件复用都做得更干净。

但有成本。小项目、Sass重度依赖、主题简单,别硬上。

不是“应该用什么”,而是“什么场景用什么”。

本文内容仅供个人学习、研究或参考使用,不构成任何形式的决策建议、专业指导或法律依据。未经授权,禁止任何单位或个人以商业售卖、虚假宣传、侵权传播等非学习研究目的使用本文内容。如需分享或转载,请保留原文来源信息,不得篡改、删减内容或侵犯相关权益。感谢您的理解与支持!

链接: https://fly63.com/article/detial/13664

相关推荐

vue定义全局变量

VUE.js 中涉及到JS全局变量:全局变量专用模块得引入、全局变量模块挂载到Vue.prototype、使用VUEX存储状态值、使用window存储变量

let与var的区别,为什么要用let?

var是全局声明,let是块级作用的,只适用于当前代码块;var变量会发生变量提升,let则不会进行变量提升;var 会造成重复赋值,循环里的赋值可能会造成变量泄露至全局

php中的$_REQUEST超全局变量

PHP $_REQUEST是用于收集HTML表单提交的数据,PHP $_REQUEST属于PHP的超级全局变量。以下实例显示了一个输入字段(input)及提交按钮(submit)的表单(form)

从.env文件中为Node.js加载环境变量

使用环境变量是配置 Node.js 程序的好方法。而且许多包或模块可以基于不同的 NODE_ENV 变量的值表现出不同的行为。存储环境变量的一种方法是将它们放在 .env 文件中。这些文件允许你指定各种环境变量及其相应的值。

在vue中实现了在样式里使用js变量的方法

在使用vue开发时,经常会封装很多的组件方便复用,那么难免就有写样式相关组件,比如需要使用时传入颜色、高度等样式参数。

js 交换变量值的方法总汇

这篇文章总结七种办法来交换a和b的变量值 。最最最简单的办法就是使用一个临时变量了 ,最后我的方案是利用了ES6的解构赋值语法 ,它允许我们提取数组和对象的值,对变量进行赋值

理解var let const区别

JavaScript中var、let、const区别?js中let和const都是es5版本新的命名规范,在此之前定定义一个变量只能用var。我们可以把let和const看做是为了弥补var的一些不足而新设计出来的

css自定义变量_初次接触CSS变量

本文的目的主要是展示CSS变量是如何工作的。随着Web应用程序变得越来越大,CSS变得越来越大,越来越多,而且很多时候都很乱,在良好的上下文中使用CSS变量,为您提供重用和轻松更改重复出现的CSS属性的机制。

Node常用的全局变量与 Inspect 调试

在 Node 中常用的全局方法有 CommonJS、Buffer、process、console、timer 等,这些方法不需要 require引入 API 就可以直接使用。如果希望有属性或方法可以“全局使用”,那就将它挂载在 Node 的global对象上:

css变量_原生css 中变量的使用

原生css 中变量的使用,这个重要的 CSS 新功能,所有主要浏览器已经都支持了。本文全面介绍如何使用它,你会发现原生 CSS 从此变得异常强大。声明变量的时候,变量名前面要加两根连词线(--),var()函数用于读取变量。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!