全部笔记

设计令牌:先命名含义,再统一数值

区分调色板与语义角色,通过消费者清单、主题映射和版本化迁移建立可预测的令牌变更。

同一个绿色为什么需要两个名字

假设一个管理后台把提交按钮背景、成功提示文字和图表增长线都设为绿色。浅色截图看起来统一。加入深色主题后,按钮要维持品牌识别,提示文字要在深背景上可读,图表又需要与另外五条数据线区分。三个消费者开始争夺同一个 green-600,调色板越细也不能消除这种冲突。

问题不是绿色不够多,而是三个值的变化理由不同。主动作、成功状态和数据系列不是同一个语义,即使此刻恰好使用相同数值,也不应该被迫同步变化。本文用这个假设后台推演令牌的建模、生成与迁移;代码是局部示意,没有声称建立完整标准格式转换器。

从消费者反推语义接口

先查按钮、消息、表格和图表真正需要什么,而不是先设计一棵很大的名称树。提交按钮需要动作背景、动作前景和焦点反馈;成功消息需要状态文字与背景;图表需要按数据类别稳定区分的颜色。每个角色记录使用位置、搭配背景和允许状态,这张清单决定抽象边界。

原始层保存调色板,语义层保存用途,组件层只处理确有不同约束的局部行为。组件直接引用语义角色,不自行选择原始色号。设计师改变品牌调色板后,仍能讨论哪些用途应该跟随、哪些需要保持独立,而不是在几十个页面搜绿色。

:root {
  --palette-brand-600: #166534;
  --palette-neutral-950: #171717;
  --action-primary-background: var(--palette-brand-600);
  --action-primary-foreground: #ffffff;
  --text-body: var(--palette-neutral-950);
  --status-success-text: #166534;
  --status-success-background: #f0fdf4;
}

[data-theme="dark"] {
  --action-primary-background: #86efac;
  --action-primary-foreground: #171717;
  --text-body: #f5f5f5;
  --status-success-text: #bbf7d0;
  --status-success-background: #14532d;
}

.primary-action {
  color: var(--action-primary-foreground);
  background: var(--action-primary-background);
}

这里的颜色只是演示映射,不代表所有组合已经满足对比要求。动作前景与背景成对变化,成功状态拥有自己的映射。主题不是把浅色值机械取反,而是重新选择能够承担同一角色的值。

如果一个名称叫 sidebar-left-green,它绑定了位置与外观,而没有说明用途。布局改动后名称就会失真。反过来,把所有边界都命名为 border-default 也可能过度合并:可编辑输入边界与纯装饰分隔线的变化要求未必一致。好的名称不是越抽象越好,而是能解释谁依赖它、为何改变它。

类型和引用图比文件层级更重要

颜色、长度、时长和字体不是同一种数据。生成工具应验证类型与单位,并区分最终值和别名引用。把所有值存成无说明的字符串,会让颜色引用到时长这种错误直到浏览器忽略声明时才出现。选用标准格式时应按实际工具支持的规范版本实现,不凭名称相似推断兼容。

别名构成依赖图。构建阶段至少检查引用存在、类型相容以及是否形成循环,再解析到对应平台输出。假设正文颜色引用主要文字,而主要文字又引用正文颜色,两个名字都存在,却没有可求出的终值。循环应使生成失败,不能静默输出空值或任选一个默认颜色。

CSS 自定义属性的运行时回退也不能替代构建检查。某些缺失引用会让使用该值的声明失效,最终外观取决于继承和级联,未必等于团队预期默认。开发环境可能被浏览器默认样式救回来,另一个主题或嵌套容器却出现不可读文字。依赖检查要在输出前拒绝问题,而不是等用户截图。

生成产物应该稳定排序、保留可追踪名称,并能关联到源定义版本。同一输入产生不同顺序的庞大差异,会增加审查噪声。设计工具与代码工具不必输出相同文件,但必须共享角色定义和版本记录;仅共享十六进制颜色仍无法证明两边讲的是同一种接口。

组件覆盖必须有独立变化理由

表格中的紧凑按钮可能需要更小内边距,却不一定需要一套新品牌颜色。此时增加组件尺寸映射有依据,复制全部动作色没有依据。组件层的职责是表达稳定的局部约束,不是让每个组件脱离语义系统。

可以用一次变更来检验抽象:提高全站正文对比时,哪些对象应该改变?如果表格文字、表单说明和正文均受影响,这是合理协调;如果图表数据编码也被改掉,说明依赖跨越了语义边界。相反,某个明确属于正文的局部组件没有变化,则可能存在未经说明的硬编码。

局部例外不需要自动禁止。某个第三方嵌入或实验模块暂时不能接入,记录所有者、原因和复查条件,比伪装成已经统一更诚实。但例外不能再被其他页面复制成第二套标准。扫描直接值时,要先判断职责,再决定迁移或保留,而不是一律删除。

一次旧颜色迁移应如何展开

假设旧版只有 --green-600,三类消费者都在用。不要把它直接重命名为主动作色,然后让成功文字和图表跟着走。先建立新角色,按实际用途分类引用者;分类不明的消费者由维护者确认,而不是按文件名批量猜测。

第一阶段增加新角色并保留旧接口,输出弃用说明。第二阶段逐组件改用新角色,每次迁移比较正常、悬浮、焦点和错误状态。第三阶段确认全部受支持消费者已经迁移,才计划删除旧接口。删除时还要考虑独立发布的组件包:当前仓库没有引用,不代表外部使用者已经升级。

这里的兼容承诺属于接口,不属于截图。别名可以暂时保持旧外观,但不能把旧绿色同时别名到两个以后会分离的语义,再声称所有消费者无损迁移。过渡阶段必须说明旧名称究竟保留哪个旧行为,必要时保留原始值直到使用者完成分类。

迁移回退也分两层。只改映射时可以恢复前版定义;已经删除名称或修改生成器时,则可能需要同时恢复接口与消费者。发布记录列出定义版本、生成工具版本和组件依赖,才能恢复一组兼容产物,而不是把一个旧 CSS 文件单独覆盖回去。

无障碍验收发生在真实组合里

正文前景在普通页面对比充足,不代表置于选中行、提示背景或图片上仍然可读。检查单位应是前景、背景、用途与状态的组合,而不是独立色块。普通文字、较大文字和需要识别的非文字控件适用的要求不同,应按 WCAG 对应成功准则判断,不能用一个阈值统治所有对象。

颜色也不能成为唯一信息通道。成功消息需要明确文字,错误输入需要可理解的说明与关联,图表需要标签或其他区分手段。增加一个叫成功色的令牌并没有实现这些行为。焦点样式还涉及边界位置、遮挡与交互顺序,令牌只能提供其中的视觉参数。

主题验收覆盖浅色、深色,以及产品承诺支持的用户偏好。不要仅把设计工具里的画板当成最终证据:实际字体、透明层叠和浏览器渲染会影响结果。禁用态也需要确认信息是否仍有阅读用途,不能因为控件不能操作,就把附近说明一起淡到看不见。

审批应该区分数值调整与语义改变

把正文前景从一个色值改为另一个色值,和把正文前景扩展为所有图表标签的共同接口,是两种不同变更。前者主要检查已有组合,后者扩大消费者集合,需要重新确认未来是否一起变化。审查说明应写清是哪一种,而不只列一张新旧数值表。

同样,增加名称不总是兼容改动。生成器若改变解析方式或输出作用域,新令牌可能覆盖已有组件的值;一个新主题若成为默认主题,也会改变用户首次进入的外观。版本规则要依据可观察行为,不能仅凭文件里是新增行就认定没有风险。

职责可以拆成三个问题:谁批准角色定义,谁维护生成工具,谁负责组件迁移。小团队可以由同一人承担,但问题仍需分别回答。否则设计师批准颜色,开发人员却顺手合并角色,双方各认为另一方已经审查了语义。发布检查留下对应决策,比规定所有变化都开冗长会议更有效。

对新角色设一个准入问题也有帮助:它是否至少有明确消费者,是否说明与现有角色的区别,是否给出主题映射与示例?暂时无法回答时,先保留局部实现或实验名称,避免把未经验证的样式变成全站承诺。后续出现第二个消费者,再根据真实共同约束抽象,而不是提前制造几十个空接口。

维护者还应定期检查无人使用的名称,但引用扫描只能覆盖已知代码。外部组件包、设计文件和运行时拼接可能不在扫描范围里。删除决定需要说明扫描边界与兼容期限;工具报告零引用是证据之一,不是自动删除授权。对仍在弃用期的接口,可以停止新增消费者,同时让旧消费者按计划迁移。

用变更演练验证系统是否可维护

选择正文前景、主动作背景和成功文字三个角色,在修改前写下预期消费者。分别改变一次映射,比较实际影响清单与预测。多出的影响提示语义耦合,缺失的影响提示旁路实现;截图差异只是定位线索,还需看实际引用关系。

再构造缺失别名、循环引用和错误类型,生成工具应明确失败。用一个仍依赖旧接口的组件验证弃用检查确实能发现它,而不只是正常输入通过。设计文件与浏览器示例核对同一版本,避免审查的是新画板、交付的是旧包。

首版不必令牌化每个像素。先覆盖反复出现且变化理由清楚的角色,完成一次真实主题切换和旧接口迁移。能够预测一次修改的影响、发现不合法依赖并恢复兼容版本,才说明系统建立了可维护契约;变量数量本身不提供这种证据。

不同平台共享含义,不一定共享同一种值

网页、移动客户端和邮件模板都可能需要主要动作前景,但它们的字体、背景和渲染能力不同。共同语义不要求直接复制相同输出文件。可以共享角色与适用条件,再由平台映射选择有效值。若邮件客户端不支持某种表达式,生成器应输出它能够使用的格式,而不是把浏览器变量原样塞进去后宣称已统一。

长度与字体尤其需要明确单位。网页里的相对单位可能依容器或用户设置变化,原生客户端有自己的尺寸与缩放体系。一个名为正文尺寸的角色可以有不同平台映射,但要保留共同任务,例如连续阅读和系统放大。转换工具不应把所有数值无条件换算成像素,削弱平台本来支持的用户偏好。

动画时长也不是孤立参数。减少动态效果的用户偏好可能需要取消位移、改变过渡方式,而非简单把时长设为零。角色应该说明它服务反馈还是装饰,以及减少动态时如何保持信息可见。状态完成不能依赖动画结束才通知业务,令牌系统负责表现参数,不承担事实状态机。

建立平台能力清单有助于避免假统一:支持哪些类型、别名如何解析、主题何时切换、用户偏好如何覆盖。缺少支持的角色明确拒绝或采用有依据的降级,不静默输出看似合法但失效的值。统一的目标是可预测行为,不是所有仓库拥有一份同名文件。

主题作用域要检查嵌套与外部组件

页面整体是浅色,一个预览区域需要深色,一个弹出菜单通过 portal 渲染到文档根节点。若主题变量只在预览容器定义,菜单可能读取全局浅色值,与触发区域不一致。主题作用域不完全等于视觉上属于哪里,实际 DOM、继承关系和组件挂载位置都影响解析。

可以选择给浮层显式传递主题身份,或把它挂在适当主题容器中,具体方案依据组件实现。不能仅提高样式优先级补掉眼前颜色,再让下一个浮层重复出错。测试包含模态、菜单、提示和第三方嵌入,核对它们读取的角色来源。嵌套主题中的焦点和选中状态也要重新验证组合。

用户设置、系统偏好和页面局部主题之间需要优先级。用户明确选择深色后,系统偏好变化是否覆盖,应该由产品定义并一致实现。初始化时先出现另一主题可能造成闪烁,处理方式涉及样式加载和状态读取,但不能为了消除闪烁把敏感用户配置随意注入公开缓存页面。

主题切换验收不是只看正文。加载态、禁用态、错误提示、图表标签和浮层都属于消费者。每个角色在这些作用域中承担相同含义,映射可不同。若某个区域必须保留独立主题,说明原因和边界,而不是让它成为无法追踪的硬编码例外。

定义变化与生成变化分别留下产物身份

语义定义不变,生成器升级仍可能改变 CSS 作用域、单位转换或名字转义。反过来,生成器不变,定义里的别名指向改变也会扩大影响。发布记录至少能区分源定义身份、工具版本与输出身份,定位问题时才知道是规则改变还是转换改变。

稳定输出允许审查真实差异。构建输入相同而输出不同,应调查时间戳、排序、环境或工具不确定行为;不能把庞大噪声当作令牌生成必然代价。可以对规范化产物做比较,但规范化不能掩盖作用域或单位这种有语义的变化。追求小差异是为了提高审查能力,不是把重要变化过滤掉。

错误处理也要明确。缺失引用不能继续使用上次成功产物而不说明构建已失败,否则预览看起来正常,提交内容却没有进入产物。保留旧版本作为显式回退可以合理,但界面和流水线都应标明正在使用哪个版本。成功定义、成功生成和成功交付分别确认。

一次可复现的演练是固定输入,生成两个平台产物,再升级工具并比较语义角色映射。对变化逐项解释,无法解释的差异先不发布。这不证明所有渲染环境正确,却可以降低构建层的不确定性,为后续真实组合验收提供可靠基础。

令牌目录应该回答使用与禁用条件

一个角色除了名称和值,还可以列出适用背景、允许状态、已知消费者和不应使用的场景。例如危险动作背景不等于所有错误文字,数据系列颜色不承担成功与失败语义。禁止条件帮助新消费者避免误用,往往比增加更多相近名字有效。

文档示例和代码示例引用同一版本,并展示真实组合。色块展示说明调色板,组件展示说明角色,两者不要混为一种验收。维护成本来自接口承诺,所以角色目录应围绕真实使用组织;没有消费者、没有边界的名称,即使分类整齐,也只是未来的维护负担。

消费者登记能帮助发现错误抽象

新组件申请使用成功背景,应说明它表达什么状态、放在哪个前景组合、为何随成功规则变化。如果实际只是品牌装饰,就不应借用成功语义。消费者登记不必是繁重审批,可以是一条明确使用理由与回归样例,让未来修改有可预测影响范围。

定期比较登记清单与代码引用,找出未说明的旁路和无人使用角色。工具只负责定位,维护者判断是迁移、记录例外还是拆分语义。这样抽象随真实需求校正,不因名称已进入目录就永远禁止重新审视。

参考资料