写死在代码里的颜色,第二次改版就会背叛你。
一、同一个蓝色,十一种写法
以下情境为教学假设,人物、引语和数字不代表经核实的真实案例。
周叙的看板工具写了两年。某次要统一主色,他全局搜索 #2F6FEB,找到十一次,分布在按钮、链接、选中态、图表和邮件模板里。改完之后,他又发现三处写的是 #2f6feb(小写),两处写的是 rgb(47,111,235),还有一处是设计稿里导出时偏了 3 个色阶的 #3070EC。
同一次改版,他花了一个下午做替换,仍然漏掉了邮件模板里的一处。用户收到的通知邮件里,按钮还是旧蓝色。
这不是粗心,而是缺少一层中间层。颜色、间距、字号直接写在组件里,每改一次就要全量搜索;搜索又依赖写法一致,而写法从来不会一致。
更麻烦的是,这个问题会随时间恶化。每加一个页面,就多几处硬编码;每接一个第三方组件,就多一套无法控制的颜色。两年后,同一份代码里会有三四套并行的视觉规则,谁也说不清哪一套是「对的」。
二、三层 token:原始值、语义、组件
设计 token 的本质是给每个视觉决定起一个名字,让组件引用名字而不是数值。
| 层级 | 例子 | 谁在用 | 特点 |
|---|---|---|---|
| 原始值 | blue-500: #2F6FEB | 语义层 | 只描述颜色本身,不含用途 |
| 语义 | color-action: blue-500 | 组件、页面 | 描述用途,换主题只改这一层 |
| 组件 | button-bg: color-action | 单个组件 | 允许组件有局部差异 |
三层不是必须全建。一个人的产品,原始值 + 语义层就够用;组件层只给少数需要独立演化的组件(按钮、输入框、卡片)留口子。
语义命名要按用途,不按颜色。 color-action、color-danger、color-surface 是用途;color-blue、color-red 是颜色。按颜色命名,换主色时语义层就失去了意义。
同义词不要建两个。 color-primary 与 color-action 若指向同一个值,早晚会有一个被改掉。一个用途一个名字,宁可名字长一点。
组件层不是逃生通道。 组件 token 只能引用语义 token,不能直接写原始值。否则三层结构退化成两层,问题原样回来。
原始值层要有刻度感。 blue-500 比 blue-main 更容易扩展,因为它在暗示「还有 400 和 600」。一个人维护时不必凑满整套刻度,但命名方式要留出位置。
语义层是唯一允许被页面直接引用的层。 页面里出现 blue-500,就说明有人绕过了中间层。这条规则可以用脚本检查,比靠自觉可靠。
三、间距基数与字号阶梯
间距和字号是两套独立的刻度,各自需要一个基数。
间距基数:4
4, 8, 12, 16, 24, 32, 48, 64
字号阶梯:基数 16,比例 1.25
13, 16, 20, 25, 31, 39
行高:正文 1.5–1.6,标题 1.2–1.3
圆角:4(小)、8(中)、12(大)
基数选 4 还是 8,取决于界面密度。信息密集的后台适合 4,阅读型产品用 8 更舒展。关键不是选哪个,而是只选一个,并且所有间距都从它派生。
字号阶梯不要超过六档。 每多一档,就要多定义一次行高、多检查一次移动端适配。六档足够覆盖标题、正文、说明、辅助四类用途。
行高要跟着字号走。 小字号需要更大的相对行高(1.6),大标题可以收紧到 1.2。固定行高会让大标题散架、正文拥挤。
圆角也要进 token。 圆角是最容易被随手写死的属性,也是视觉一致性最直观的部分。三个档位够用,多了就是设计稿在替你决定。
图标尺寸也应纳入系统。 16、20、24 三个档位足够覆盖按钮内、导航栏与空状态。图标尺寸若随手写,视觉上会比颜色更早暴露出「拼装感」。
间距不要用「感觉差不多」的数。 13px、18px、22px 这类值一旦出现,就说明有人绕过了系统。它们不会立刻出问题,但会在半年后让界面看起来「说不出哪里不对」。
字号阶梯要跟着用途命名,不跟着数字命名。 text-body、text-caption、text-title 比 text-14、text-12 更耐用,因为后者会在你换基数时全部变成谎言。
行高与字号成对定义。 只给字号不给行高,每个使用处都要重新决定一次,最后的结果一定是同一个字号有四种行高。
四、一个人维护得住的规模
设计系统的失败很少因为做得太少,多数因为做得太多。
一个单人产品的 token 预算
颜色 ≤ 11 (主色 1 + 中性 7 + 语义 3)
间距 ≤ 8
字号 ≤ 6
行高 ≤ 3
圆角 ≤ 3
阴影 ≤ 2
合计 ≈ 30
超过 40 个,通常意味着有人在为个别页面开特例。特例本身不是错,但要显式记录:它属于哪个页面、为什么不能复用现有 token、什么时候回收。
先迁移,再扩展。 已有产品引入 token,第一步是把现有硬编码值归并成最少的集合,而不是先设计一套理想的刻度。归并后你会发现,47 个色值其实只有 9 个是不同的。
迁移按「改动频率 × 影响面」排序。 先改颜色(改版最频繁),再改间距(影响布局),最后改字号(影响阅读节奏)。一次只迁一类,每类迁完截图对比,确认没有视觉变化。
token 文件要只有一份来源。 无论用 CSS 变量、JSON 还是框架配置,最终要有一处是权威的。设计稿里的值若与代码不一致,以代码为准,并反过来修正设计稿。
给 token 写一句注释。 每个语义 token 后面写清「用在哪、不能用在哪」。半年后你自己也会忘记 color-muted 和 color-subtle 的区别。
特例要集中,不要散落。 某个图表需要第四种蓝色时,把它写成该图表组件内部的常量并注明原因,不要新增一个全局 chart-blue。局部特例一旦进入全局层,就会被别处误用。
不要为了「以后可能换主题」提前抽象。 深色模式、多品牌、白标都是真实需求,但在没有第二个消费方之前,三层结构会变成负担。先做两层,需要时再加。
迁移要留可回退的一步。 先在一两个页面替换 token,观察一周;确认没有视觉回归后,再推广到全部页面。一次性全量替换,出问题时你分不清是 token 定义错了,还是某处替换漏了。
五、行动清单与复算验收
- 统计当前代码里出现的颜色、间距、字号、圆角的不同取值数
- 把颜色归并到 11 个以内,间距归并到 8 个以内
- 建立原始值层,命名按「色相 + 明度」或数字刻度
- 建立语义层,命名按用途(action、surface、danger、muted)
- 把组件里的硬编码值替换为语义 token,一次只替换一类
- 每类替换后截图对比,确认没有非预期视觉变化
- 统计 token 总数,超过 40 个时逐个说明理由
- 在 token 文件里为每个语义项写一句使用边界注释
验收标准:能报出 token 总数,以及改一次主色需要修改几处。 改主色仍需全局搜索的,说明还没建立语义层。
使用边界与实操验收
本文讲 token 的结构与规模,不涉及具体框架怎么实现,那属于前端工程化专题:从 Vite 到 WebAssembly 的全栈实践 ;色板与对比度属于第 03 篇;间距如何影响首屏布局属于第 05 篇;上线前的检查项属于第 09 篇。
本文也不主张「所有项目都该有设计系统」。只有一两个页面的落地页,直接把值写在样式里更划算。需要 token 的信号是:同一个值出现了三次以上,且你已经开始漏改。
验证以改动成本为准:记录一次主色变更实际花的时间。第一次可能是两小时,第二次应当降到十分钟以内。降不下来,说明还有组件在绕过 token。
另一个可复算的指标是「孤儿值」数量:用脚本扫描样式文件,统计不在 token 表里的颜色与间距值。目标不是零(内联样式与第三方组件总会有),而是逐月下降。
验收的最后一步是改一次试试。把主色换成另一个色相,记录改动点数量与耗时;如果超过三处、超过半小时,说明语义层还没有真正成为唯一入口。
关键参考与核验范围
本文的刻度与预算为教学演示。核验日期:2026-10-24。
- Material Design 3: Design tokens :设计 token 的分层方式与命名实践,可作为结构参照。
- WCAG 2.1: Contrast (Minimum) :颜色 token 落地后仍需满足的对比度下限。
上述两条只用于说明公开实践,不构成对任何设计系统或框架的推荐。token 的具体命名与层级可按项目规模调整,本文的 30 个预算不是行业标准。
系列导航
- 上一篇:视觉识别的最小系统:一色、一字、一形状
- 下一篇:首屏与信息架构:一页只回答一个问题
- 回到目录:一个人把产品做得像样:品牌、视觉识别与设计系统
- 相关阅读:前端工程化专题:从 Vite 到 WebAssembly 的全栈实践 、视觉识别的最小系统:一色、一字、一形状
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。