wave-contrast-audit

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.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "wave-contrast-audit" with this command: npx skills add kaipengyu/wave-contrast-audit-skill/kaipengyu-wave-contrast-audit-skill-wave-contrast-audit

WAVE Contrast Audit

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.

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

Core Workflow

Step 1 — Navigate to the live page (not the WAVE report)

Always run axe-core on the actual page, not the WAVE report wrapper.

Navigate to: https://example.com/page.html

Step 2 — Inject and run axe-core for exact color pairs

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.

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

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

Step 4 — Find the CSS source rules

Use Grep to locate every CSS rule producing the failing color:

# 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.css

Read the surrounding context to confirm background color and element role.

Step 5 — Calculate compliant replacement values

For white text on dark backgrounds (rgba(255,255,255, A)):

RequiredMin alpha on #022b45Min alpha on #034f7dMin alpha on #7a4f99
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:

  • #1499e8 on white = 3.1:1 (fail) — use #0d77c4 for button bg (4.72:1)
  • #1499e8 as text on white — use #0d77c4

For brand blue as text on dark backgrounds:

  • #1499e8 on #022b45 = 2.8:1 (fail) — use #8accf4 (~5.0:1)

For muted gray on white/cream:

  • #7a7878 on white = 4.4:1 (fail) — darken to #686666 (5.7:1)
  • #7a7878 on #ededed = 3.7:1 (fail) — #686666 also passes cream (4.9:1)

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

Step 6 — Apply fixes efficiently

Batch all changes in a single Python pass to avoid making 50+ Edit calls:

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: rgba(255,255,255,.X) 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.

Step 7 — Verify fixes

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

Common Failure Patterns

PatternFailing valueFix
Sidebar nav links on dark bgrgba(255,255,255,.5-.62)Raise to .82
Section labels on dark bgrgba(255,255,255,.18-.25)Raise to .75
Body text on dark bgrgba(255,255,255,.52-.62)Raise to .85
Small labels/captions on dark bgrgba(255,255,255,.38-.45)Raise to .82
Any text on purple bg (#7a4f99)alpha < .85Raise to .88
CTA button (white on #1499e8)3.1:1Change bg to #0d77c4
Muted gray on white#7a7878Darken to #686666
Brand blue as text on white#1499e8Change to #0d77c4
Brand blue as text on dark#1499e8Change to #8accf4
Brand palette 25% tint on color bge.g. #ded3e6 on purpleUse var(--white)

WCAG 2.2 AA Thresholds (Quick Reference)

Text sizeRequired ratio
Normal (< 18px regular, < 14px bold)4.5 : 1
Large (>= 18px regular, >= 14px bold)3.0 : 1
UI components and icons3.0 : 1

WAVE False Alarms & How to Avoid Them

WAVE evaluates the DOM after JavaScript runs, at the initial scroll position (top of page). This causes predictable false positives that must be distinguished from real failures.

1 — GSAP scroll animations (opacity: 0 → reported as 1:1 contrast)

gsap.fromTo() immediately applies the FROM state as an inline style the moment the script runs. Every element targeted by a scroll-triggered fade-in gets opacity: 0 before any scrolling occurs. WAVE computes the rendered text color including opacity, so color: #022b45 at opacity: 0 becomes rgba(2,43,69,0) — a 1:1 contrast failure.

Diagnosis: Click the WAVE error icon — the element disappears. WAVE's code panel shows color: rgba(r,g,b,0) with alpha = 0.

Fix: Replace opacity with GSAP's autoAlpha in all content animation vars.

// BEFORE — causes WAVE false alarms
gsap.set('.hero-title', { opacity: 0, y: 30 });
gsap.to('.hero-title',  { opacity: 1, y: 0, duration: 1 });

// AFTER — WAVE skips visibility:hidden elements, no false alarm
gsap.set('.hero-title', { autoAlpha: 0, y: 30 });
gsap.to('.hero-title',  { autoAlpha: 1, y: 0, duration: 1 });

autoAlpha: 0 sets opacity: 0; visibility: hidden. WAVE's documented rule: "Errors are only reported on elements visible to users"visibility: hidden is explicitly excluded from contrast checks.

Apply to all gsap.set, gsap.fromTo, and gsap.to calls that fade content in. Do not apply to background image wrappers or decorative elements that already use aria-hidden.

2 — Background-image elements (reported as 1:1 contrast)

Elements that use a child div for background-image (and a ::after gradient overlay for the dark backdrop) have no background-color on the container. WAVE can't load images or evaluate gradients, so it falls back to white. White text on white = 1:1.

Diagnosis: Hero sections with photos behind text flagged at 1:1 despite visually clear contrast.

Fix: Add a solid background-color fallback matching the darkest gradient value. This is invisible to visual users (the image covers it) but gives WAVE a computable dark color.

/* The gradient goes to rgba(4,14,25,.97) at the bottom */
.hero-a {
  background-color: #040e19; /* fallback for contrast tools that can't read background-image */
  /* rest of rules... */
}
.hero-b-left {
  background-color: #040e19;
}

Note: hero-c uses background: #040f1c directly (no child image div), so it never had this issue.

3 — Low-opacity decorative glyphs (chevrons, watermarks)

Elements with opacity: 0.4 or similar applied via CSS/GSAP inherit a dimmed version of their parent's text color. WAVE computes this as the rendered color and flags it.

Common culprit: Dropdown chevrons () inside nav links styled with opacity: .4.

Fix: aria-hidden="true" — these are purely decorative; the link text already conveys the interactive meaning.

<!-- BEFORE -->
<a href="#">Our Work <span class="snav-chev">▾</span></a>

<!-- AFTER -->
<a href="#">Our Work <span class="snav-chev" aria-hidden="true">▾</span></a>

Similarly for oversized watermark text (giant serif letters behind content):

<div class="tq-bg" aria-hidden="true">"</div>
<div class="co-type-bg" aria-hidden="true">Water</div>

4 — Form labels not programmatically linked

WAVE flags <label> elements that exist visually but are not connected to their input via for/id. These show as "Missing form label" errors, not contrast errors.

Fix: Add matching for and id attributes.

<!-- BEFORE -->
<label class="f-label">Email Address</label>
<input class="f-input" type="email" placeholder="alex@example.com">

<!-- AFTER -->
<label class="f-label" for="f-email">Email Address</label>
<input id="f-email" class="f-input" type="email" placeholder="alex@example.com">

False alarm vs. real failure — decision table

WAVE symptomClick element → it disappears?Likely causeFix
1:1 contrast on headingYesGSAP opacity: 0 inlineSwitch to autoAlpha
1:1 contrast on hero textNoNo background-color fallback on image containerAdd solid dark background-color
Low contrast on tiny glyphN/ADecorative icon with low opacityaria-hidden="true"
Low contrast on label textNoReal failure — muted color on light bgDarken the color token
Missing form labelN/ANo for/id associationAdd for/id pair

Limitations

  • axe-core skips elements where background is a CSS gradient, background-image, or filter — use WAVE's eyedropper tool for those manually
  • 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 aria-hidden="true" rather than contrast fixes
  • WAVE evaluates all DOM elements regardless of scroll position — elements hidden by scroll-triggered JS animations will be flagged unless using autoAlpha or visibility: hidden

Resources

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Security

Voidly Agent Relay

Give agents encrypted private messaging — send, receive, discover, and call other AI agents with post-quantum E2E encryption. No API key needed. Zero config.

Registry SourceRecently Updated
Security

Certcheck

SSL/TLS certificate checker and analyzer. Inspect SSL certificates for any domain, check expiration dates, verify certificate chain, detect security issues,...

Registry SourceRecently Updated
760Profile unavailable
Security

Credential Tester

A little tool to play with Windows security credential-tester, c. Use when you need credential-tester capabilities. Triggers on: credential-tester.

Registry SourceRecently Updated
780Profile unavailable
Security

XHS-Ops: Xiaohongshu Operations Toolkit

Xiaohongshu (小红书) end-to-end operations skill: hot topic research, post writing with built-in audit, automated commenting with rate limiting, and cover image...

Registry SourceRecently Updated
00Profile unavailable