配了五年设计系统,每次想让文字在任何背景上都清晰可读,都要写一长串 luminance 计算——今天 CSS 把这件事彻底原生化了
写过五年设计系统,每次想让文字在任何背景上都清晰可读,都要先写一套 luminance 计算函数——一个函数算亮度、一个函数比较黑白、最后再套回去。上线之后还得担心用户换主题时 JS 还没跑起来那几百毫秒的 FOUC。
2026 年了,这件事被一个 CSS 函数从根上换了。
contrast-color() 是什么
Chrome 147、Firefox 146、Safari 26 全部稳定支持的 CSS 函数,CSS Color Level 5 规范,2026 年 4 月进入 Baseline。你给它一个颜色,它返回黑白两者中对比度更高的那个——全程浏览器在样式计算阶段完成,零 JS、零库、零构建步骤。
.button {
background-color: var(--brand-color);
color: contrast-color(var(--brand-color));
}
用户换一个主题色,文字色自动跟着变。没有 useEffect、没有 reflow、没有 hydration 时机问题。相当于把每个设计系统迟早会写的那段 luminance helper 永久删掉了。
三个典型场景
1. 设计系统动态主题色
.card {
--bg: var(--card-color, #2d5a27);
background-color: var(--bg);
color: contrast-color(var(--bg));
}
不用再维护一套 --card-text-on-dark / --card-text-on-light 的变量组合。一个 --bg 走天下,深色背景自动出白字,浅色背景自动出黑字。
2. 深浅模式无缝切换
:root {
--bg: #f5f5f5;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #1a1a2e;
}
}
body {
background-color: var(--bg);
color: contrast-color(var(--bg));
}
纯 CSS 实现,用户切系统主题不需要任何 JS 逻辑参与。
3. 悬停动态变色
.card {
--bg: oklch(0.6 0.15 250);
background: var(--bg);
color: contrast-color(var(--bg));
}
.card:hover {
--bg: oklch(from var(--bg) calc(l + 0.12) c h);
}
配合相对颜色语法,悬停时背景亮度提升,文字色自动从白切换成黑,零 JS。
一个必须知道的坑:中灰色陷阱
contrast-color() 只返回黑或白,目标 WCAG AA(4.5:1)。当背景是中等明度的颜色(#767676 附近)时,黑白两个选项都在阈值边缘徘徊,结果对用户实际可读性很差。
解决思路:把设计系统约束在明度高或低的范围,不要让主色调经过中灰区间。配合 oklch 的 lightness clamp 可以低成本做到:
.badge {
/* 把输入推到已验证的亮度范围再对比 */
--safe: oklch(from var(--badge-color) clamp(0.2, l, 0.45) c h);
background: var(--safe);
color: contrast-color(var(--safe));
}
渐进增强写法
.button {
background-color: var(--brand);
color: #fff; /* 老浏览器兜底 */
}
@supports (color: contrast-color(red)) {
.button {
color: contrast-color(var(--brand));
}
}
不支持的浏览器拿到白字(需要人工确认白字在你的深色 band 里可读),现代浏览器拿到计算值。这是标准渐进增强,不是 polyfill——因为 contrast-color(var(--x)) 在构建时无法求值,必须运行时在浏览器里算。
和 JS 颜色库的真实对比
| contrast-color() | chroma-js | polished | |
|---|---|---|---|
| 体积 | 0 kB | ~14 kB | ~11 kB |
| 计算时机 | 样式计算时 | JS 运行时 | JS 运行时 |
| 依赖 | 无 | 需安装 | 需安装 |
| 配合 CSS 变量 | ✅ 原生 | 需传值下去 | 需传值下去 |
| 暗色模式 | 一个声明 | 需要两套变量 | 需要两套变量 |
三步下一步
- 找你的 luminance helper:项目里搜
relative luminance/getLuminance/readableColor,如果搜到了,说明你已经需要这个函数了 - 迁移按钮/标签/卡片:把
color: var(--text-on-${variant})换成color: contrast-color(var(--bg)),配合--bg变量走天下 - 约束你的色板:确认现有品牌色的 oklch lightness 落在
l < 0.25或l > 0.45区间,不在这个区间的颜色不要进主题色变量
WebAIM 统计了五年,83.9% 的页面仍然对比度不及格。工具没少出,数字几乎没动过。问题不在工具,在于这个计算从来就不该在 JS 层做——现在浏览器把这件事接过去了。
评论区
登录后可评论。