配了五年设计系统,每次想给按钮加个状态色都要手算对比度——今天这件事被 CSS 一个函数彻底原生化了

设计系统的时候,给按钮加个新的主题色,往往意味着要给这个颜色配一套文字颜色——背景色深了就配白字,浅了就配黑字,如果还有 hover/active 状态,那配色工作量直接翻倍。

这件事以前只能靠两种方式硬扛:要么每个颜色变体都手写一组「背景+文字」变量对,要么用 JS 在运行时根据 luminance 动态计算。两种方案都有代价:变量对难维护,JS 计算增加运行时开销。

Chrome 147(2026 年 4 月)开始,所有主流浏览器陆续支持了一个 CSS 函数,叫 contrast-color()。它的作用非常直接:传入任意一个颜色,它会返回黑或白——哪个对比度高返回哪个。你只需要管背景色,文字颜色交给浏览器判断。

.button {
background-color: var(–brand-color);
color: contrast-color(var(–brand-color));
}

这个 --brand-color 换成紫色、黄色、青色,文字颜色会自动适配,不需要任何额外变量,也不需要 JS。

原理:WCAG 的亮度公式在浏览器里跑了一遍

contrast-color() 底层用的是 WCAG 2.1 的相对亮度公式:L = 0.2126×R + 0.7152×G + 0.0722×B。浏览器算出输入颜色的亮度值 L,如果 L 大于 0.5 返回黑色,否则返回白色。如果两个对比度恰好相等(极端中灰色),默认返回白色。

规范把算法标记为「UA 定义」,意思是浏览器以后可以换成更先进的 APCA 对比度算法而不破坏现有代码。这是给未来留的口子。

真实场景:动态主题色 + 无障碍文字

最合适的用法是设计系统的语义颜色令牌。每个组件只声明一次 --color-primary,而不需要 --color-primary-bg--color-primary-text 成对出现:

@property –primary-color {
syntax: “<color>”;
initial-value: #6366f1;
inherits: true;
}

.btn-primary {
background-color: var(–primary-color);
color: contrast-color(var(–primary-color));
}

用户切暗色主题时只需要改变 --primary-color 一个值,文字颜色自动重新计算。这比写两套 CSS 变量要干净得多。

进阶:配合 color-mix() 做出品牌感

contrast-color() 只返回纯黑或纯白,对品牌要求高的场景可能过于生硬。配合 color-mix() 可以做出带色调的辅助文字:

.card {
background-color: var(–swatch);
color: color-mix(in oklch, var(–swatch) 15%, contrast-color(var(–swatch)));
}

una.im 的测试显示:浅色背景混入 10-25%,深色背景混入 30-40%,通常既能过 WCAG 对比度检查,又比纯黑/纯白更有品牌感。

局限性:不要神话它

contrast-color() 不是银弹。MDN 的警告说得很清楚:中色调背景(比如 #2277d3 这个蓝)配纯黑文字,小字号下阅读体验并不好。它只解决「有没有足够对比度」的问题,不解决「看起来是否好看」的问题。

另外,它目前只返回黑或白,不能自定义候补颜色。如果你有严格的设计规范需要特定文字色,这个函数帮不上忙。

下一步

如果你现在维护一个有多套主题色的项目,可以做一件事:把组件里所有「背景色+文字色」的成对变量,替换成一个背景变量加一行 color: contrast-color(),然后在几个典型颜色上跑一遍 WCAG 对比度检查。只要过了 4.5:1,这个替换就是安全的。

不需要任何 build tool,不需要 JS,一个 CSS 函数把设计系统里最繁琐的配色逻辑卸掉了。

评论区

0 条评论

登录后可评论。

阿柯·前端架构 14 阅读