应使用 WCAG 2.1 的 relative-luminance 和 contrast-ratio 判断文字颜色,因 HSL 的 lightness() 不符合人眼感知亮度;正确做法是 contrast-ratio($bg, #fff) >= 4.5,而非 lightness($color) > 50%。

直接用 lightness() 切黑白字色会翻车
很多项目用 lightness($color) > 50% 判断背景够不够深,来决定文字用黑还是白。这在黄、浅青、淡紫等色相上几乎必然出错——比如 #ffcc00 的 lightness() 是 ~60%,但人眼觉得它很亮,黑字发灰;而 #e6f7ff 的 lightness() 是 ~95%,实际仍能清晰显示黑字。根本原因是 HSL 的 L 值不反映人眼感知亮度,纯红和纯绿的 lightness() 都是 50%,但相对亮度分别是 0.21 和 0.72。
必须用 WCAG 2.1 的 relative-luminance 和 contrast-ratio
真正可靠的判断依据是:背景色与文字色之间的对比度比值 ≥ 4.5(AA 级可读性)。这个比值由两者的相对亮度推导而来,公式已标准化:
-
relative-luminance对每个通道做非线性转换(sRGB gamma 校正),再加权求和:0.2126 * R + 0.7152 * G + 0.0722 * B -
contrast-ratio($bg, $text)=(max($l1, $l2) + 0.05) / (min($l1, $l2) + 0.05) - 所以判断白字是否可用,只该写:
contrast-ratio($bg, #fff) >= 4.5,而不是看lightness()
以下函数经 Dart Sass 1.33+ 实测可用(LibSass 已淘汰,不建议兼容):
@function relative-luminance($color) {
$r: red($color) / 255;
$g: green($color) / 255;
$b: blue($color) / 255;
$r: if($r < 0.03928, $r / 12.92, pow(($r + 0.055) / 1.055, 2.4));
$g: if($g < 0.03928, $g / 12.92, pow(($g + 0.055) / 1.055, 2.4));
$b: if($b < 0.03928, $b / 12.92, pow(($b + 0.055) / 1.055, 2.4));
@return 0.2126 * $r + 0.7152 * $g + 0.0722 * $b;
}
<p>@function contrast-ratio($bg, $text) {
$l1: relative-luminance($bg);
$l2: relative-luminance($text);
$l-max: max($l1, $l2);
$l-min: min($l1, $l2);
@return ($l-max + 0.05) / ($l-min + 0.05);
}</p><p>@function is-dark-enough-for-white-text($bg) {
@return contrast-ratio($bg, #fff) >= 4.5;
}主题色板里别硬写 $text-light/$text-dark 变量
多主题项目常见错误:为每个主题单独定义 $light-text-color 和 $dark-text-color,然后靠人工配对。这样维护成本高,且一旦背景色微调(比如换了个更浅的灰色),对应的文字色未必还满足 4.5 对比度。
立即学习“前端免费学习笔记(深入)”;
正确做法是把文字色逻辑收口到函数里:
- 所有背景色统一走
is-dark-enough-for-white-text($bg)判断 - 组件中直接写:
color: if(is-dark-enough-for-white-text($bg), #fff, #000); - 如果主题 map 中某项背景色是动态计算出来的(比如
lighten($primary, 40%)),函数仍能实时评估,无需预设结果
注意:Sass 编译时无法读取运行时 CSS 变量值,所以这套逻辑只适用于编译期确定的主题色(即 $themes map 里的静态颜色值)。
对比度检查函数在主题切换链路中的位置很关键
它不能放在最外层全局样式里「一劳永逸」地生成所有文本规则,而应嵌入具体组件或原子类的生成逻辑中。例如:
- 按钮组件:
.btn { background-color: $bg; color: if(is-dark-enough-for-white-text($bg), #fff, #333); } - 卡片组件:
.card { background: map-get($theme, surface); color: if(is-dark-enough-for-white-text(map-get($theme, surface)), #fff, map-get($theme, text)); }
最容易被忽略的一点:这些函数只在编译时执行一次。如果你用 JavaScript 动态切换 data-theme 并依赖 CSS 变量更新样式,那 is-dark-enough-for-white-text() 就完全不生效——此时必须改用运行时 JS 检查,或提前为每套主题生成好带白/黑文字的完整 CSS 规则。


















