wave-contrast-audit
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseWAVE Contrast Audit
WAVE 对比度检测
Systematically find and fix every color contrast violation on a live page using axe-core for precise data, then trace failures back to CSS source rules.
使用axe-core获取精准数据,系统性地查找并修复在线页面上的所有颜色对比度不符合项,然后追溯问题至CSS源规则。
When to Use This Skill
何时使用该技能
- A WAVE report shows contrast errors on a deployed page
- User shares a WAVE report URL ()
wave.webaim.org/report#/... - Asked to "fix contrast issues" or "make the site WCAG AA compliant"
- After a design system token change that may have introduced failures
- 已部署页面的WAVE报告显示对比度错误
- 用户分享WAVE报告链接()
wave.webaim.org/report#/... - 被要求“修复对比度问题”或“使网站符合WCAG AA标准”
- 设计系统令牌变更后可能引入不符合项时
Core Workflow
核心工作流程
Step 1 — Navigate to the live page (not the WAVE report)
步骤1 — 导航至在线页面(而非WAVE报告页)
Always run axe-core on the actual page, not the WAVE report wrapper.
Navigate to: https://example.com/page.html始终在实际页面上运行axe-core,而非WAVE报告的包装页面。
Navigate to: https://example.com/page.htmlStep 2 — Inject and run axe-core for exact color pairs
步骤2 — 注入并运行axe-core以获取精准颜色配对
This gives you precise foreground/background hex values, contrast ratios, CSS selectors, and font sizes — far more actionable than clicking WAVE icons one by one.
javascript
async () => {
const script = document.createElement('script');
script.src = 'https://cdnjs.cloudflare.com/ajax/libs/axe-core/4.9.1/axe.min.js';
document.head.appendChild(script);
await new Promise(r => script.onload = r);
const results = await axe.run({ runOnly: ['color-contrast'] });
// Deduplicate by unique fg+bg pair
const pairs = {};
for (const v of results.violations) {
for (const node of v.nodes) {
for (const rel of node.any) {
if (rel.data?.fgColor) {
const key = `${rel.data.fgColor}__on__${rel.data.bgColor}`;
if (!pairs[key]) {
pairs[key] = {
fg: rel.data.fgColor,
bg: rel.data.bgColor,
ratio: rel.data.contrastRatio,
fontSize: rel.data.fontSize,
fontWeight: rel.data.fontWeight,
required: rel.data.expectedContrastRatio,
count: 0,
selector: node.target[0]
};
}
pairs[key].count++;
}
}
}
}
return {
total: results.violations.reduce((a, v) => a + v.nodes.length, 0),
pairs: Object.values(pairs).sort((a, b) => a.ratio - b.ratio)
};
}这能为你提供精准的前景/背景十六进制值、对比度比值、CSS选择器和字体大小——比逐个点击WAVE图标更具可操作性。
javascript
async () => {
const script = document.createElement('script');
script.src = 'https://cdnjs.cloudflare.com/ajax/libs/axe-core/4.9.1/axe.min.js';
document.head.appendChild(script);
await new Promise(r => script.onload = r);
const results = await axe.run({ runOnly: ['color-contrast'] });
// Deduplicate by unique fg+bg pair
const pairs = {};
for (const v of results.violations) {
for (const node of v.nodes) {
for (const rel of node.any) {
if (rel.data?.fgColor) {
const key = `${rel.data.fgColor}__on__${rel.data.bgColor}`;
if (!pairs[key]) {
pairs[key] = {
fg: rel.data.fgColor,
bg: rel.data.bgColor,
ratio: rel.data.contrastRatio,
fontSize: rel.data.fontSize,
fontWeight: rel.data.fontWeight,
required: rel.data.expectedContrastRatio,
count: 0,
selector: node.target[0]
};
}
pairs[key].count++;
}
}
}
}
return {
total: results.violations.reduce((a, v) => a + v.nodes.length, 0),
pairs: Object.values(pairs).sort((a, b) => a.ratio - b.ratio)
};
}Step 3 — Interpret the results
步骤3 — 解读结果
For each unique pair, note:
- fg + bg — the actual rendered hex values (rgba already resolved to hex)
- ratio — current contrast (e.g. 3.1:1)
- required — 4.5 (normal text) or 3.0 (large text >= 18px / >= 14px bold)
- selector — CSS selector pointing to the failing element
- count — how many elements share this exact pair
对于每一组唯一的颜色配对,记录:
- 前景色 + 背景色 — 实际渲染的十六进制值(rgba已转换为十六进制)
- 对比度比值 — 当前对比度(如3.1:1)
- 要求比值 — 4.5(常规文本)或3.0(大文本 >=18px / >=14px粗体)
- 选择器 — 指向不符合元素的CSS选择器
- 计数 — 有多少元素使用该颜色配对
Step 4 — Find the CSS source rules
步骤4 — 查找CSS源规则
Use to locate every CSS rule producing the failing color:
Grepbash
undefined使用定位所有导致颜色不符合的CSS规则:
Grepbash
undefinedSearch for the failing value (rgba or hex)
Search for the failing value (rgba or hex)
grep -n "rgba(255,255,255,.45)|#7a7878|var(--steel)" src/styles.css
grep -n "rgba(255,255,255,.45)|#7a7878|var(--steel)" src/styles.css
Or search by selector
Or search by selector
grep -n ".sb-num|.btn-water" src/styles.css
Read the surrounding context to confirm background color and element role.grep -n ".sb-num|.btn-water" src/styles.css
阅读上下文以确认背景颜色和元素角色。Step 5 — Calculate compliant replacement values
步骤5 — 计算合规的替代值
For white text on dark backgrounds ():
rgba(255,255,255, A)| Required | Min alpha on | Min alpha on | Min alpha on |
|---|---|---|---|
| 4.5:1 | >= 0.75 (use 0.82) | >= 0.75 (use 0.82) | >= 0.85 (use 0.88) |
| 3.0:1 | >= 0.50 (use 0.60) | >= 0.52 (use 0.60) | >= 0.60 (use 0.68) |
For brand blue on light backgrounds:
- on white = 3.1:1 (fail) — use
#1499e8for button bg (4.72:1)#0d77c4 - as text on white — use
#1499e8#0d77c4
For brand blue as text on dark backgrounds:
- on
#1499e8= 2.8:1 (fail) — use#022b45(~5.0:1)#8accf4
For muted gray on white/cream:
- on white = 4.4:1 (fail) — darken to
#7a7878(5.7:1)#686666 - on
#7a7878= 3.7:1 (fail) —#edededalso passes cream (4.9:1)#686666
Manual luminance calculation (when needed):
Contrast = (L_lighter + 0.05) / (L_darker + 0.05)
L = 0.2126 * R_lin + 0.7152 * G_lin + 0.0722 * B_lin
R_lin = ((R/255 + 0.055) / 1.055)^2.4 for values > 0.04045
R_lin = (R/255) / 12.92 for values <= 0.04045深色背景上的白色文本():
rgba(255,255,255, A)| 要求比值 | 在 | 在 | 在 |
|---|---|---|---|
| 4.5:1 | >= 0.75(建议使用0.82) | >= 0.75(建议使用0.82) | >= 0.85(建议使用0.88) |
| 3.0:1 | >= 0.50(建议使用0.60) | >= 0.52(建议使用0.60) | >= 0.60(建议使用0.68) |
浅色背景上的品牌蓝色:
- 白色背景上的= 3.1:1(不符合)—— 按钮背景使用
#1499e8(4.72:1)#0d77c4 - 白色背景上的文本 —— 使用
#1499e8#0d77c4
深色背景上的品牌蓝色文本:
- 上的
#022b45= 2.8:1(不符合)—— 使用#1499e8(约5.0:1)#8accf4
白色/奶油色背景上的柔和灰色:
- 白色背景上的= 4.4:1(不符合)—— 加深至
#7a7878(5.7:1)#686666 - 背景上的
#ededed= 3.7:1(不符合)——#7a7878在奶油色背景上也符合要求(4.9:1)#686666
手动亮度计算(必要时):
Contrast = (L_lighter + 0.05) / (L_darker + 0.05)
L = 0.2126 * R_lin + 0.7152 * G_lin + 0.0722 * B_lin
R_lin = ((R/255 + 0.055) / 1.055)^2.4 for values > 0.04045
R_lin = (R/255) / 12.92 for values <= 0.04045Step 6 — Apply fixes efficiently
步骤6 — 高效应用修复
Batch all changes in a single Python pass to avoid making 50+ Edit calls:
python
with open('styles.css', 'r') as f:
css = f.read()
changes = [
('--steel: #7a7878', '--steel: #686666'),
('.btn-water { background: var(--vibrant-blue)',
'.btn-water { background: #0d77c4'),
# Add targeted string replacements for each failing rule...
]
results = []
for old, new in changes:
if old in css:
css = css.replace(old, new)
results.append(f" changed: {old[:50]}")
else:
results.append(f" NOT FOUND: {old[:50]}")
with open('styles.css', 'w') as f:
f.write(css)
print('\n'.join(results))Warning:appears in backgrounds, borders, and box-shadows — not only text color. Always read context before replacing. Use targeted string matches (include surrounding lines) rather than broad single-value replacements.rgba(255,255,255,.X)
在单次Python处理中批量完成所有更改,避免进行50多次编辑操作:
python
with open('styles.css', 'r') as f:
css = f.read()
changes = [
('--steel: #7a7878', '--steel: #686666'),
('.btn-water { background: var(--vibrant-blue)',
'.btn-water { background: #0d77c4'),
# Add targeted string replacements for each failing rule...
]
results = []
for old, new in changes:
if old in css:
css = css.replace(old, new)
results.append(f" changed: {old[:50]}")
else:
results.append(f" NOT FOUND: {old[:50]}")
with open('styles.css', 'w') as f:
f.write(css)
print('\n'.join(results))警告:会出现在背景、边框和盒阴影中——不仅是文本颜色。替换前务必阅读上下文。使用针对性的字符串匹配(包含周围代码行)而非宽泛的单值替换。rgba(255,255,255,.X)
Step 7 — Verify fixes
步骤7 — 验证修复效果
Re-run the Step 2 script on the updated page. Target: 0 color-contrast violations.
If the file is only local (not yet deployed), navigate Playwright directly:
Navigate to: file:///path/to/page.html在更新后的页面上重新运行步骤2的脚本。目标:0项颜色对比度不符合项。
如果文件仅在本地(尚未部署),直接通过Playwright导航:
Navigate to: file:///path/to/page.htmlCommon Failure Patterns
常见不符合模式
| Pattern | Failing value | Fix |
|---|---|---|
| Sidebar nav links on dark bg | | Raise to |
| Section labels on dark bg | | Raise to |
| Body text on dark bg | | Raise to |
| Small labels/captions on dark bg | | Raise to |
| Any text on purple bg (#7a4f99) | alpha < .85 | Raise to |
| CTA button (white on #1499e8) | 3.1:1 | Change bg to |
| Muted gray on white | | Darken to |
| Brand blue as text on white | | Change to |
| Brand blue as text on dark | | Change to |
| Brand palette 25% tint on color bg | e.g. | Use |
| 模式 | 不符合值 | 修复方案 |
|---|---|---|
| 深色背景上的侧边栏导航链接 | | 透明度提升至 |
| 深色背景上的章节标签 | | 透明度提升至 |
| 深色背景上的正文文本 | | 透明度提升至 |
| 深色背景上的小标签/说明文字 | | 透明度提升至 |
| 紫色背景(#7a4f99)上的任何文本 | 透明度 < .85 | 提升至 |
CTA按钮(深色背景上的白色文本 | 3.1:1 | 将背景改为 |
| 白色背景上的柔和灰色 | | 加深至 |
| 白色背景上的品牌蓝色文本 | | 改为 |
| 深色背景上的品牌蓝色文本 | | 改为 |
| 彩色背景上的品牌调色板25%色调 | 例如紫色背景上的 | 使用 |
WCAG 2.2 AA Thresholds (Quick Reference)
WCAG 2.2 AA 阈值(快速参考)
| Text size | Required ratio |
|---|---|
| Normal (< 18px regular, < 14px bold) | 4.5 : 1 |
| Large (>= 18px regular, >= 14px bold) | 3.0 : 1 |
| UI components and icons | 3.0 : 1 |
| 文本尺寸 | 要求对比度比值 |
|---|---|
| 常规(<18px常规字体,<14px粗体) | 4.5 : 1 |
| 大尺寸(>=18px常规字体,>=14px粗体) | 3.0 : 1 |
| UI组件和图标 | 3.0 : 1 |
Limitations
局限性
- axe-core skips elements where background is a CSS gradient, , or
background-image— use WAVE's eyedropper tool for those manuallyfilter - WAVE counts each text node individually; axe deduplicates by color pair — expect axe to report fewer total violations than WAVE (e.g. 53 axe vs 187 WAVE)
- Hover and focus states are not evaluated by either tool — test manually
- Decorative elements (watermarks, icon chevrons, spacers) should receive
rather than contrast fixes
aria-hidden="true"
- axe-core会跳过背景为CSS渐变、或
background-image的元素——这些元素需手动使用WAVE的吸管工具检测filter - WAVE会逐个统计文本节点;axe会按颜色配对去重——axe报告的总不符合项数量会少于WAVE(例如axe报告53项 vs WAVE报告187项)
- 悬停和聚焦状态不会被任一工具评估——需手动测试
- 装饰性元素(水印、图标箭头、间隔符)应添加而非修复对比度
aria-hidden="true"