存活的令牌
构建一个能够在下一次视觉刷新后存活的设计令牌系统。三层结构、语义命名、真实世界模式。
为什么大多数令牌系统无法在重新设计中存活
大多数设计令牌系统在两年内被弃用,因为它们编码的是当下的视觉值,而不是背后的语义意图。一个名为 blue-500 的令牌永远被锁定在蓝色值上;而一个名为 accent-primary 的令牌能够存活任何颜色变化,因为下一个设计师会将其映射到新系统使用的任何蓝色等效值。
将令牌系统分为存活和消亡的单一规则:按用途命名令牌,而不是按外观。accent-primary、surface-base、text-secondary、danger-bg。永远不要 blue-500、gray-900、red-light。语义名称是契约;值是实现细节。
三层架构
第一层:原始值。原始颜色、间距、排版值。blue-50、blue-100、blue-200...、blue-900。space-1、space-2...、space-12。这些作为基础规模存在,很少改变。
第二层:语义层。有明确用途命名的令牌映射到基础层。accent-primary 映射到 blue-500,accent-hover 映射到 blue-600,surface-base 映射到 gray-50,text-primary 映射到 gray-900。组件引用这些令牌,而不是直接引用基础层。
第三层:组件作用域。映射到语义层令牌的组件级令牌。button-primary-bg 映射到 accent-primary,card-border 映射到 border-subtle,nav-active-bg 映射到 accent-emphasis。组件使用这些令牌。
当品牌焕新时,你改变基础层色阶以及可能的语义层到基础层的映射。组件作用域令牌保持不变地流转。重新设计在数天内而非数月内发布。
编码什么,保留什么硬编码
编码:颜色、间距、字体排版(字族、字号、字重、行高)、边框圆角、阴影、动画时长和缓动函数、断点宽度。
不编码:特定于布局的值(文章模板上的 1280 最大宽度)、一次性的定位偏移、仅在单个组件中独有的动画编排。这些应该内联编码;将它们编码成令牌会制造一个自相矛盾的系统。
试金石:其他组件是否会合理地使用这个相同的值?如果是,编码它。如果否,硬编码它。
跨栈实现
在令牌边界处使用 CSS 自定义属性。在 :root 中定义基础层和语义层,在 [data-theme="dark"] 中覆盖。组件引用语义令牌,而不是基础层。
通过从同一源生成的 TypeScript 文件来访问 JS 可用令牌。Style Dictionary、Theo 或小型定制生成器从单一 JSON 或 YAML 源生成 CSS 变量和 TS 导出。单一源意味着 JS 动画和样式组件与样式表保持同步。
在单个页面(Storybook 页面或网站本身的 /design 页面)中记录该系统,这样未来的贡献者就能找到它。不易被发现的令牌系统会在几个月内被不知道它存在的人所绕过。