返回博客
Reference 2026-04-23

颜色系统: RGB, HSL, OKLCH

为什么OKLCH正在2026年的设计系统中取代HSL。

设计系统正在从HSL迁移到OKLCH,原因不是赶时髦。HSL有一个可测量的缺陷——它的亮度轴对人眼撒谎——而OKLCH用一个真正源自感知实验的模型修正了它。理解其机制,你就知道什么时候切换划算、锋利的边缘在哪里。

RGB与Hex: 硬件坐标

color: #ff6633;

color: rgb(255 102 51);

sRGB三元组描述的是发光体强度,经过伽马编码使每通道8位用在人眼最敏感的区间。作为存储和交换格式没问题;作为设计操作空间则毫无用处:没有任何一个通道可以调整来得到「同一色相,稍微暗一点」。靠微调RGB通道生成的派生调色板,色相和饱和度会同时漂移。

HSL: 几何变换,而非感知变换

color: hsl(15 100% 60%);

HSL把RGB立方体重新排列成圆柱——色相是角度,饱和度是半径,亮度是高度。它计算便宜、易于推理,因此当了二十年CSS的主力。缺陷在于:HSL的亮度是RGB值的一个公式,不是感知亮度的度量。hsl(60 100% 50%)(黄色)和 hsl(240 100% 50%)(蓝色)同为L=50%,但对任何人类观察者来说黄色都明显亮得多。后果在调色板中复利放大:用HSL生成的「均匀」亮度梯度台阶明显不均,固定L旋转色相会改变感知亮度——这就是HSL生成的分类颜色在图表上看起来混乱的原因。

OKLCH: 感知坐标

OKLCH是OKLab的圆柱形式,由Björn Ottosson于2020年发表,并被CSS Color Level 4采纳。它对现代色貌数据做了数值拟合,同时修正了CIELAB的蓝色色相扭曲和HSL的亮度谎言。

color: oklch(0.7 0.15 45);

  • L — 感知亮度,0到1。L相等的两种颜色看起来真正一样亮。
  • C — 彩度,从0(灰色)向上,实践中最饱和的可显示颜色约在0.37封顶。与HSL饱和度不同,它是绝对值,不相对于色域。
  • H — 以度为单位的色相角,按感知均匀分布。

改变日常工作的特性:三个轴相互独立。改变L,色相不会漂移;固定L和C旋转H,得到的每种颜色都有相同的视觉分量。

--brand-100: oklch(0.95 0.04 250);

--brand-300: oklch(0.80 0.10 250);

--brand-500: oklch(0.65 0.15 250);

--brand-700: oklch(0.45 0.12 250);

--brand-900: oklch(0.28 0.08 250);

这是从一个色相数字生成的完整深浅色阶,且台阶看起来均匀——HSL永远做不到的事。CSS Color 5的相对颜色语法让派生变成声明式:oklch(from var(--brand-500) calc(l + 0.1) c h) 就是「品牌色,亮一档」。

色域: 新的边界情况

OKLCH描述颜色时不管屏幕能否显示。高彩度值会超出sRGB,有些甚至超出Display P3。浏览器会做色域映射到最近的可显示颜色,但依赖它就意味着发布你从未真正见过的颜色。实用护栏:

  • 系统化调色板保持中等彩度(与文本相邻的颜色大约0.15以下),让所有台阶都能在sRGB中存活。
  • 高彩度的P3颜色留给点缀用途,并放在 @media (color-gamut: p3) 之后。
  • 每个色值都用转换器核对,不要盲信数学。

自2023年起所有常青浏览器都已支持。对遗留目标,用OKLCH编写并让PostCSS输出RGB回退;先写普通声明、再跟一个 @supports (color: oklch(0 0 0)) 覆盖的模式不用工具也能行。

要避免的两个错误

  • 相等的ΔL不等于符合WCAG的对比度。 WCAG 2.x对比度用的是另一套亮度公式,所以OKLCH推导出的看起来不错的颜色对仍可能过不了检查器,反之亦然(这正是APCA想修复的缺陷)。对比度永远要单独测量。
  • 在错误的空间里插值。 渐变和 color-mix() 在某些上下文默认使用sRGB,会在互补色之间产生灰色死区。color-mix(in oklch, var(--a), var(--b)) 则按感知插值。

迁移现有调色板

转换遗留调色板从一个机械步骤开始:把每个hex色值翻译成OKLCH,然后看数字。大多数手工调校的调色板会立刻暴露不一致——跨色相的brand-500色值很少共享同一个L值,这解释了为什么有些颜色总显得「更重」。务实的路径是:已上线UI的现有hex值原样保留,按台阶用统一的L和C在OKLCH中定义新色值,然后逐屏替换组件。暗色模式下要抵制简单的L反转:高彩度颜色在暗背景上会晕染和震颤,所以暗色主题除了翻转亮度还需要降低彩度。亮色模式的 oklch(0.55 0.15 250) 应搭配手工调整的 oklch(0.75 0.10 250) 这样的暗色变体,而不是仅靠公式计算出来的值。

在 sdk.is/color-picker 的取色器中对比同一颜色的hex、RGB、HSL和OKLCH表示——建立彩度直觉最快的方法就是拖动滑块、盯着数字变化。