accessibility-audit

verified

b1922a73-d881-4bf5-b168-593614a66f6d

Audit a UI against WCAG basics — semantic HTML, keyboard navigation, contrast, alt text, ARIA — and fix the violations. Use to make an interface usable by everyone.

Metadata

Skill ID
b1922a73-d881-4bf5-b168-593614a66f6d
Version
1
Owner
387274b7-2891-478b-81b8-e11d5adb9319
Tags
accessibilitya11ywcagkeyboardcontrastariasemantic-html
Signature
verified
Integrity
OK
Content hash
d6858253360ca698b191f0a3784356a855576c98204a76218e33c9ff3ecc69d5
Created
2026-08-15T05:24:21Z

Skill file

Raw skill file (markdown source)
# Accessibility Audit

Use when auditing a UI for accessibility — combining automated scans with the manual checks no tool can do, then fixing the real violations.

## Step 1: Automated Scan

### Lighthouse (Chrome DevTools)
1. Open DevTools → Lighthouse tab
2. Check "Accessibility" category
3. Run audit (use a mobile preset for mobile apps)

### axe DevTools (more detailed)
```bash
# axe-core via CLI (headless)
npm install -g @axe-core/cli
axe http://localhost:3000 --exit

# Playwright + axe
pip install playwright pytest-playwright
```

```python
# playwright + axe-core
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page()
    page.goto("http://localhost:3000")
    page.add_script_tag(path="axe.min.js")
    results = page.evaluate("axe.run()")
    for violation in results["violations"]:
        print(violation["id"], "—", violation["impact"], "—", len(violation["nodes"]), "instances")
    browser.close()
```

## Step 2: Manual Checks (what automation misses)

Automated tools catch ~30-50% of accessibility issues. These must be checked by hand:

### Keyboard-Only Traversal
1. Load the page, **do not touch the mouse**.
2. Press `Tab` — can you reach every interactive element?
3. Check focus is **visible** (outline) at every step.
4. `Enter`/`Space` activates buttons; arrow keys work in menus.
5. Is there a "skip to content" link as the first tab stop?
6. `Esc` dismisses modals; focus returns to the trigger.

### Focus Order
- Does focus follow the visual order (top-left → bottom-right)?
- After closing a modal, does focus return to the button that opened it?

### Alt Text Quality
- Every `<img>` has meaningful `alt` (or empty `alt=""` if decorative).
- Alt text describes *purpose*, not just "image of a dog" but what the dog image conveys.

## The Fix Checklist

| Issue | Fix |
|---|---|
| `<div onclick>` used as a button | Use `<button>` — free keyboard + focus + semantics |
| Input without label | `<label for="email">` or `aria-label` |
| No visible focus | `:focus-visible { outline: 2px solid #4a90d9 }` |
| Low contrast text | Darken text or lighten background (ratio ≥4.5:1) |
| `<h1>` then `<h3>` | Fix heading hierarchy — no skipped levels |
| Image without alt | Add descriptive `alt` or empty `alt=""` if decorative |
| Color-only status indicator | Add text/icon alongside color (e.g., "● Active") |
| Table without headers | Add `<th scope="col">` / `scope="row"` |

```html
<!-- ❌ BAD — div as button, no label, no focus -->
<div class="btn" onclick="save()">Save</div>
<input type="text" placeholder="Email">

<!-- ✅ GOOD — semantic button, labeled input -->
<button type="button" onclick="save()">Save</button>
<label for="email">Email</label>
<input id="email" type="text" placeholder="you@example.com">
```

## WCAG Levels

| Level | Meaning | Target |
|---|---|---|
| **A** | Minimum — no keyboard traps, alt text, semantic structure | Always required |
| **AA** | Contrast 4.5:1, focus visible, consistent navigation | Standard target for most products |
| **AAA** | Contrast 7:1, sign language, no time limits | Only for specialized accessibility products |

**Target AA** for a general product. AAA is aspirational and often conflicts with other requirements.

### Contrast Ratios (WCAG AA)
- **Normal text**: 4.5:1
- **Large text** (≥18pt or 14pt bold): 3:1
- **UI components / icons**: 3:1

```bash
# Quick contrast check in DevTools → inspect element → 
# use the color picker's contrast ratio display
```

## ARIA: When to Use (and When Not)

> **First rule of ARIA: no ARIA is better than bad ARIA.**

Use ARIA **only** when native HTML can't express the semantics:
```html
<!-- Native is always preferred -->
<button>  <!-- over <div role="button" tabindex="0"> -->
<nav>     <!-- over <div role="navigation"> -->
```

```html
<!-- ARIA is appropriate for custom widgets -->
<div role="alert" aria-live="assertive">Saved successfully</div>
<div role="tablist">
  <button role="tab" aria-selected="true" aria-controls="panel1">Tab 1</button>
</div>
<div id="panel1" role="tabpanel">...</div>
```

## Guardrails

- **Never** use ARIA to patch fundamentally broken HTML — a `<div role="button">` is worse than a real `<button>`.
- **Never** audit only color contrast and call it done — keyboard access is the most commonly broken and most commonly skipped.
- **Never** add `aria-label` to hide an unlabeled input without also considering the visual label.
- **Never** trap keyboard focus without a clear, documented escape (Esc key).

## Pitfalls

- **Relying on ARIA to patch bad HTML**: `role="button"` doesn't give you keyboard handling, focus management, or disabled states. Use real elements.
- **Auditing only contrast and ignoring keyboard**: Contrast is easy to measure, so it's over-reported. Keyboard traps and missing focus indicators are the real blockers for screen-reader and motor-impaired users.
- **Decorative images with descriptive alt**: A border/divider image with `alt="decorative blue line"` is noise. Use `alt=""` so screen readers skip it.
- **`aria-hidden` on focusable elements**: Hiding an element with a focusable child (like a modal close button) creates a keyboard trap. Focusable descendants of `aria-hidden` elements must be removed from the tab order.
- **Testing with the mouse**: "I can click it, so it works" — but a keyboard user can't. Always run the keyboard traversal yourself.

## Verify / Checklist

- [ ] Automated scan (axe/Lighthouse) returns zero critical/serious violations
- [ ] Full keyboard traversal works: every interactive element reachable via Tab
- [ ] Focus is visible at every step (outline present)
- [ ] "Skip to content" link is the first tab stop
- [ ] All images have appropriate alt text (or `alt=""` for decorative)
- [ ] All inputs have associated labels
- [ ] Heading hierarchy has no skipped levels
- [ ] Contrast meets WCAG AA (4.5:1 normal text, 3:1 large text)
- [ ] Modals return focus to trigger on close; Esc dismisses
- [ ] No ARIA is used where native HTML would suffice
- [ ] Screen-reader smoke test (VoiceOver/NVDA) passes for the main flow

Attached files

No attached files.