颜色
颜色系统是一小组渐变,按角色命名,并根据它们实际渲染的背景进行验证。大多数颜色错误都是系统性错误:一个孤立选取的值、一个因为看起来合适就借用的令牌、一对从未被测量过的色组。
永远不要报告一个你没有测量过的对比度值,也不要估计一个你可以计算出的颜色。颜色是界面中少数几个有精确答案的问题之一,所以请给出精确答案。
对比度要求属于 hr-better-accessibility。表面、阴影和图标颜色不在此技能范围内。
匹配项目的颜色系统
复用项目的令牌和表示法。为了修复一个值而添加的第二种表示法会使调色板更难理解。一个一致的十六进制系统胜过散布着 oklch() 的十六进制。
一旦令牌存在,就通过 lint 规则或守卫测试来强制执行,该测试会在令牌文件之外出现颜色字面量时失败,同时为那些需要原始值的地方设置一个简短的白名单。一条没有检查的“使用令牌”规则会随着时间的推移而偏离。
对于新系统,oklch() 是最好的默认选择,因为它的数值行为符合下面渐变规则所描述的规则。在其他情况下,一个颜色库会以项目自己的表示法产生相同的渐变(color-formats.md)。
系统是渐变,而不是颜色
一个中性渐变,一个强调渐变,以及产品实际渲染的状态渐变。一个没有被任何地方引用的 warning 渐变,是零像素的维护成本。只有当两样东西必须一眼就能区分开来时,第二个强调色相才有其地位。
每一步都有其职责
渐变不是一个可以通过目视挑选的渐变。每一步的存在是因为某个角色需要它:页面背景、组件悬停、边框、实心填充、正文文本。不要生成一个没有角色消耗的步骤。Tailwind 的 50–950 和 Radix 的 1–12 两种约定都可以映射到这些角色(palette-structure.md)。
按色相命名原始值,按角色命名语义值
原始值命名一个值(--blue-500),并且永远不直接应用于组件。语义令牌命名一个职责(--color-text-secondary),指向一个原始值,并且是组件引用的唯一层级。
这条分界线是使主题化成为可能的关键。没有它,深色模式意味着要审计每个用法,以确定哪个是“强调色”,哪个只是想要蓝色(token-naming.md)。
只在角色中使用令牌
永远不要因为一个令牌当前的值合适就借用它。一个用作文本颜色的分隔线,在边框变淡后就会跟着变淡。如果一个角色没有令牌,请添加令牌。
保持渐变中的色相恒定
一个结构良好的渐变由四个属性定义:
- 步骤遵循一个有意的感知亮度渐进(palette-generation.md)。
- 色相从头到尾保持恒定。
- 鲜艳度在渐变的中间达到峰值,并在两端下降。
- 步骤在亮端的分布比暗端更密集。
两端都止于纯黑和纯白之前,因为纯黑和纯白无法承载任何色相。使用颜色库而不是目视估算(palette-generation.md)。
一色一义
在整个界面中,一种颜色只用于一个目的,将色相相差 15° 范围内的任何颜色视为同一种颜色。如果强调色表示可交互,那么静态文本上的那种色相会告诉用户点击某些不可点击的东西,而一个看起来是中性的交互元素也同样具有误导性。颜色永远不是意义的唯一载体,这由 hr-better-accessibility 负责。
每个视图只填充一个操作
当填充色表示主要重点时,一个主要操作使用它,而同级的其他操作保持中性。将颜色放在背景上,而不是标签上。一个填充按钮从远处看就是主要的;而中性按钮上的强调色文本看起来像链接。
多个彩色背景是没问题的,只要它们表示不同的状态或类别,而不是作为同级的竞争元素。
测量实际渲染的色对,然后报告
测量一个前景色与它实际渲染的背景色之间的对比度,而不是页面背景色。当一对色组失败时,报告这对色组、它的测量值以及它缺失的阈值,然后保持颜色不变。它们是设计决策。只有在被要求时才更改它们,并在更改后重新测量(contrast.md)。
选择渐变的插值空间
插值空间是一种外观选择,而不是正确性设置。
in oklab是最好的默认选择:亮度均匀,无色相意外。in oklch会绕着色相环移动,而不是穿过中间,从而保持鲜艳度,并在两个色相之间的所有过渡色上产生丰富的色彩。当两种色相的渐变在中间变灰时,可以使用它。- sRGB 默认值 会使中间点变暗和变灰。大多数已有的界面都是这种效果,因为这是你不指定任何选项时得到的结果。
参见 color-usage.md。
在完成之前
| 错误 | 修复 |
|---|---|
| 项目中已有令牌,却使用了原始值 | 复用或添加角色令牌,使用项目的表示法 |
在十六进制代码库中插入了一个孤立的 oklch() 值 | 保持既定的表示法,除非正在进行迁移 |
在组件中直接使用像 --blue-500 这样的原始值 | 用一个语义令牌指向它 |
令牌按其外观命名(--color-blue-button)或首次使用(--color-sidebar-gray) | 按其角色命名:--color-accent-solid、--color-bg-surface |
--color-primary 表示品牌色,而 --color-text-primary 表示正文文本 | 保留 accent 给品牌色;让 primary 表示“其组中最突出”的 |
| 语义令牌在其角色之外使用(如分隔线作为文本色) | 为缺失的角色添加令牌;永远不要按值借用 |
| 通过改变 HSL 亮度构建的渐变 | 根据感知亮度重新构建,并保持色相恒定 |
| 在整个范围内均匀间隔的渐变 | 收紧亮端,直到 50 和 100 呈现为两个不同的表面 |
| 不同色相使用相同的饱和度值 | 匹配每种色相自身最大值的比例,而不是原始值 |
| 与强调色相冲突的状态色相 | 移动它,直到破坏性操作和主要操作并排时能区分开来 |
| 通过机械地反转亮色调色板制作的深色模式 | 反转作为起点,然后降低鲜艳度,加宽暗端,并重新检查每一对色组 |
prefers-color-scheme 设置一些令牌,而 .dark 类设置其他令牌 | 选择一个切换机制并在整个项目中使用 |
| 通过改变色相来修复对比度 | 改变亮度,这是对比度响应的通道 |
| 没有 sRGB 备选方案的 P3 颜色 | 先声明 sRGB 值,然后在 @media (color-gamut: p3) 内部覆盖 |
报告
严重性。 HIGH 使内容无法阅读或分配了误导性的语义颜色。MEDIUM 是明显的主题、令牌或色域失败。LOW 是孤立的细节问题。
验证。 没有浏览器:令牌值、每个声明颜色的色域、存在两个主题块,以及从声明的令牌对计算出的对比度。有浏览器:文本实际渲染的背景,包括透明度及其下方的任何图像,在亮色和深色两种模式下测量。失败的色对被报告,而不是被重绘。报告你无法运行的每个检查为 未验证。
格式。 将发现结果按其违反的原则分组,按严重性排序,每个根因一行,列出其出现的每个位置:
| 严重性 | 位置 | 之前 | 之后 | 原因 |
|---|
位置 是 路径/到/文件:行。原因指出违反的原则和用户影响。
当有任何 HIGH 问题未解决时,报告 阻塞,否则报告 批准,并将剩余问题留在表中作为待办工作。永远不要对你没有检查的范围批准。如果没有任何可报告的,说明“没有可操作的颜色发现”并报告验证情况。