对比度不是"看起来清不清楚"这种主观判断,WCAG 给了一个确定的公式,算出来是 1:1 到 21:1 之间的一个数。达标与否只看这个数是否跨过阈值。
先把颜色拆成 R、G、B 三个 0–255 的通道值,对每个通道做归一化:
c = c8 / 255
若 c ≤ 0.03928: c_lin = c / 12.92
否则: c_lin = ((c + 0.055) / 1.055) ^ 2.4
L = 0.2126 × R_lin + 0.7152 × G_lin + 0.0722 × B_lin
那个 2.4 次幂是 sRGB 的伽马校正——屏幕上的颜色不是线性叠加的,必须还原到线性空间再加权。三个权重不一样是因为人眼对绿色最敏感,蓝色最不敏感。
顺带一提,WCAG 2.x 文本里的阈值写的是 0.03928,有些实现按 sRGB 规范取 0.04045。两者差异极小,不会影响任何达标的判定结果。
对比度 = (L_较亮 + 0.05) / (L_较暗 + 0.05)
那个 +0.05 是为了避免纯黑(L=0)时出现除以零。取值范围是 1:1(完全相同)到 21:1(纯黑配纯白)。
写成代码:
function channel(c8) {
const c = c8 / 255;
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
}
function luminance(r, g, b) {
return 0.2126 * channel(r) + 0.7152 * channel(g) + 0.0722 * channel(b);
}
function contrast(a, b) {
const l1 = luminance(...a);
const l2 = luminance(...b);
return (Math.max(l1, l2) + 0.05) / (Math.min(l1, l2) + 0.05);
}
contrast([255, 255, 255], [118, 118, 118]); // 4.54
contrast([255, 255, 255], [89, 89, 89]); // 7.00
| 内容类型 | AA(最低要求) | AAA(增强) |
|---|---|---|
| 正文文本 | 4.5:1 | 7:1 |
| 大号文本 | 3:1 | 4.5:1 |
| 非文本(图标、输入框边框、图表线条) | 3:1 | 无明确要求 |
"大号文本"的界定要记准:18pt 及以上(约 24px),或 14pt 及以上的粗体(约 18.66px)。16px 的常规字号不算大号,必须按 4.5:1 走。
非文本那条来自 WCAG 2.1 的 1.4.11,指的是"理解内容所必需的"图形——一个纯装饰的背景纹样不要求,但一个只有描边、没有文字的搜索图标边框必须达到 3:1。
| 前景 | 背景 | 对比度 | 判定 |
|---|---|---|---|
#767676 灰 |
白 | 4.54:1 | AA 正文刚好过线 |
#595959 灰 |
白 | 7.00:1 | 达到 AAA |
#999999 灰 |
白 | 2.85:1 | 不达标,这是最常踩的坑 |
| 白 | #007BFF 蓝 |
3.98:1 | 大号文本可用,正文不达标 |
最后一行值得注意:Bootstrap 那套经典蓝配白字,用作按钮大字号可以,用作 14px 的正文链接是不达标的。很多"看起来很清楚"的品牌色,实测都在 3.5–4.2 这个尴尬区间。
#999 压到 #767676 就能过 AA,压到 #595959 能过 AAA。opacity: 0.6 后的实际颜色取决于它叠在什么上面,背景是图片或渐变时根本算不出来。要改就改成确定的色值。/* 不推荐:实际颜色依赖背景,无法验算 */
.muted { color: #333; opacity: 0.5; }
/* 推荐:写成确定的色值,可以算、可以验 */
.muted { color: #767676; }
disabled)。WCAG 明确豁免。WCAG 1.4.1 要求"不能仅靠颜色传达信息"。表单报错只把输入框描边变红是不够的——色盲用户看不出区别。必须同时给出文字说明或图标。
最后提一句:APCA 是 WCAG 3 的候选对比度算法,对深色模式的判断比现有公式更合理,但它还不是正式规范,验收请继续按 4.5:1 这套来。
Chrome 和 Edge 的 DevTools 里,选中元素后在 Styles 面板点击颜色值旁边的小方块,会弹出取色器并直接显示该颜色与背景的对比度,以及是否达到 AA / AAA。这是最快的单点检查方式。
整页扫描用自动化工具:Lighthouse 的 Accessibility 分项和 axe DevTools 插件都能批量列出对比度不达标的元素,给出具体颜色值和实测比值。它们用的就是上面这套公式,所以和手算结果一致。
但工具只能检查静态且可计算的颜色。下面几种情况它们会跳过或报错,必须人工验算:
rgba() 或 opacity);浅色模式下达标的配色,反转成深色模式不一定达标。常见情况是背景换成深灰后,原来的品牌色文字在深底上对比度掉到阈值以下——因为对比度取决于两者的相对亮度差,不是"一深一浅"就够了。
做法是给深色模式单独定义一套前景色,通常是在 HSL 里提高 L 分量(而不是提高饱和度),然后对每个组合重新验算一遍。
键盘用户靠焦点框定位,所以焦点指示器(focus ring)同样要求 3:1 的对比度。很多设计为了"干净"直接写 outline: none,这是明确的无障碍缺陷。正确做法是换成自定义的高对比度描边,而不是把它删掉。