// Created At 2026-04-18// P3
// CSS · Vue · Design System · Engineering · Architecture

当设计令牌遇上原子化CSS:一次整合失败的反思与融合之道

个人开发者尝试UnoCSS映射设计令牌,当天失败,但收获了一套务实的分工方案

起点:我有一套完整的设计令牌

我的个人项目 moongate-ui 一开始只有三份CSS文件:

  • colors.css:浅色/深色双主题,40+语义化颜色变量(--ui-primary--ui-bg-muted……)
  • layout.css:间距、圆角、阴影、动效、字体、断点等布局令牌
  • main.css:全局组件样式、月晕协议、潮汐触发等核心契约

这套令牌系统不依赖任何框架,任何组件都可以通过 var(--ui-spacing-md)var(--ui-primary) 获取设计约束。它稳定、直观、可维护。

诱惑:UnoCSS 的轻量与原子化

我听过 Tailwind 的“笨重”名声,但 UnoCSS 号称按需生成、零运行时、超轻量。作为个人开发者,我渴望“不用写CSS类名”的体验——直接在模板里堆 flex p-4 text-center,不用切文件,不用想命名。

于是某个夜晚,我决定:把设计令牌“映射”到 UnoCSS 上,既保留设计系统,又享受原子化书写。

碰撞:一个晚上的挣扎

我写了 uno.config.ts,试图把每个 --ui-* 变量映射成原子类:

colors: {
  primary: 'var(--ui-primary)',
  muted: 'var(--ui-bg-muted)',
  // ...
}

然后在 Button.vue 里把原来的 scoped 样式全部替换成原子类:

<button :class="cn('bg-primary text-white', 'bg-bg-muted', 'border-border-subtle')">

问题很快暴露:

  • 类名冗余bg-bg-mutedborder-border-subtle,读起来像口吃。
  • 映射维护成本:CSS 变量改一处,UnoCSS 配置也要改,单一真实源变成两个。
  • 条件逻辑爆炸:为了处理 neutral 颜色,写 color === 'neutral' ? 'text-dim' : 'text-'+color
  • 调试困难:浏览器里看到 bg-bg-muted,得反向查找它对应哪个CSS变量。

当晚我就删掉了所有映射代码,回到了原始方案。

顿悟:设计令牌就是最轻量的原子化框架

看着 bg-bg-muted,我突然问自己:为什么不直接写 background-color: var(--ui-bg-muted) 呢?

我的设计令牌系统本身已经提供了所有设计约束。UnoCSS 宣称的“设计约束”能力,我的CSS变量全都有。它剩下的唯一价值就是“快速书写”——即语法糖。

更深一层:--ui-primary 映射成 bg-primary,本质上是在“用 CSS 封装 CSS”。UnoCSS 在编译阶段生成 .bg-primary { background-color: var(--ui-primary); } 这样的代码。既然最终都是这行 CSS,我直接在 scoped 样式里写它,不是更直接吗?

反思:原子化CSS与设计令牌并非对立

我的经历并不证明原子化CSS不好,而是证明:当你的项目已经拥有一套成熟的设计令牌时,强行把令牌映射成原子类是多余的

但这不等于要完全放弃原子化工具。我后来找到了合理的分工:

样式类型使用方式理由
布局、间距(通用)、定位、flex/gridUnoCSS 原子类(flex, p-4, gap-2无需映射,开箱即用,书写快
颜色、主题间距、圆角、阴影、动效组件 scoped 样式 + var(--ui-*)语义化、可维护、无映射成本

这里有一个重要的边界:响应式设计。原子化CSS的 p-sm md:p-md 写法确实比在 scoped 样式里写媒体查询更简洁。对于复杂的响应式布局,我仍然会使用 UnoCSS 的响应式变体,但仅限于与设计令牌无关的通用属性(如 flexgridpadding 的数值直接用 UnoCSS 的默认比例,不强行映射我的令牌)。这算是一种务实的妥协。

升华:DTCG 与设计令牌的行业趋势

值得一提的是,W3C 旗下的设计令牌社区组(DTCG)已在 2025 年底发布首个稳定规范,推动设计令牌成为跨平台通用语言。这意味着“以令牌为中心”的架构,正成为行业共识。

原子化工具可以消费设计令牌,但不应绑架设计令牌。将令牌硬编码成特定工具的原子类,相当于把自己的设计系统锁死在该工具的语法上。而直接使用 CSS 变量,则是框架无关、面向未来的选择。

融合之道:给同样处境的开发者建议

如果你和我一样,已经有一套完整的设计令牌系统(CSS变量、Design Tokens),但想尝试原子化CSS,我的建议是:

  1. 不要映射颜色、主题间距、圆角等核心令牌。这些应该直接通过 var(--ui-*) 在 scoped 样式中使用。
  2. 只使用原子化工具提供的通用布局类flex, p-4, gap-2, relative……)。这些与你的设计系统无关,且无需任何映射。
  3. 警惕“魔法数字”:避免在模板里写 p-3.5gap-11 这种硬编码值,它们会破坏设计令牌的契约。如果你发现自己经常需要偏离令牌系统,说明你的令牌定义可能不够灵活,需要扩展而非绕过。
  4. 对于响应式布局,可以继续使用原子化工具的响应式变体(md:p-4),但尽量只用于通用属性,不用于颜色/主题。
  5. 如果原子化工具让你觉得“为了用而用”,完全可以不用。原生CSS + 设计令牌已经足够清晰、可维护。

成本/收益对比

为了更直观地展示两种方案的差异,我画了一个简表:

维度映射方案 (原子类+令牌)融合方案 (scoped样式+令牌+布局原子类)
书写速度极快(全在模板)中等(CSS + 模板切换)
维护成本高(双向同步配置)低(单一真实来源)
语义清晰度差(bg-bg-muted优(var(--ui-bg-muted)
响应式便捷度高(md:p-4中(需写媒体查询)
组件库分发强绑定工具框架无关,仅依赖CSS
调试体验差(需查映射)好(直接看到变量)

对于个人项目,维护成本、语义清晰度、分发兼容性往往比极致的书写速度更重要。因此融合方案胜出。

结论:设计令牌优先,原子化可选

UnoCSS 和 Tailwind 是好工具,但它们不是设计系统的替代品。设计令牌才是地基,原子化只是上面的一层涂料。当你已经有一块坚实的地基时,是否涂上这层涂料,取决于你愿不愿意接受那点语法糖带来的维护成本。

至少对我来说,不划算

最终,我的 Button.vue 回到了最原始的样子:

<style scoped>
.mg-button {
  background-color: var(--ui-primary);
  color: white;
  padding: var(--ui-spacing-md) var(--ui-spacing-lg);
}
</style>

简单、直接、语义化。这才是设计令牌该有的用法。

如果这篇文档对你有帮助,可以请我喝杯咖啡 ☕️
Ali PayWechat Pay
评论区
© 2026 MOONGATE