Loading...
Loading...
Audit a live webpage for WCAG color contrast failures using axe-core injected via Playwright, then map every failing color pair to its exact CSS rule and apply compliant fixes. Use when a WAVE report shows contrast errors or when asked to fix accessibility contrast issues on a deployed page.
npx skill4agent add kaipengyu/wave-contrast-audit-skill wave-contrast-auditwave.webaim.org/report#/...Navigate to: https://example.com/page.htmlasync () => {
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)
};
}Grep# Search for the failing value (rgba or hex)
grep -n "rgba(255,255,255,.45)\|#7a7878\|var(--steel)" src/styles.css
# Or search by selector
grep -n "\.sb-num\|\.btn-water" src/styles.cssrgba(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) |
#1499e8#0d77c4#1499e8#0d77c4#1499e8#022b45#8accf4#7a7878#686666#7a7878#ededed#686666Contrast = (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.04045with 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)
Navigate to: file:///path/to/page.html| 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 |
| 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 |
background-imagefilteraria-hidden="true"