WCAG 2.1 AA Audit Skill
Audit a page for WCAG 2.1 AA accessibility issues by tracing its render chain and checking source code.
1. Identify the Page
Parse $ARGUMENTS for a page name or URL path:
- Page names:
homepage, about, contact, login, dashboard, product-detail, etc.
- URL paths:
/, /about/, /products/123/, /blog/my-post/, etc.
Locate the entry point for the target page. Look for:
- Route configuration: e.g.,
routes.php, web.php (Laravel), urls.py (Django), route files in src/routes/ or app/router/
- Page registry or CMS config: any database-driven or config-driven page lookup
- Direct file mapping: e.g.,
pages/about.tsx (Next.js), src/views/About.vue (Vue), public_html/about.php
- Catch-all routers:
router.php, index.php, or framework entry points
From the entry point, identify the controller/handler, view/template, and includes/components for the target page. If the argument is homepage or /, start from the root index file.
2. Ask for External Input
Before doing any audit work, use AskUserQuestion to ask:
Do you have external audit results (e.g., WAVE summary) to include?
Options:
- Yes — I'll paste WAVE results: Wait for the user to paste their WAVE output. Then proceed.
- Yes — other input: Wait for the user to provide their context (focus areas, known issues, etc.). Then proceed.
- No — run source-only audit: Skip straight to the render chain trace and run all built-in checks.
How to use external input
When the user provides input:
- WAVE results: Cross-reference each reported issue against the source code. For each WAVE finding, locate the exact
file:line, confirm the issue, and propose a fix. Promote confirmed WAVE findings to Critical severity. Also run the full standard audit — WAVE may have missed structural issues.
- Focus areas: Run the full audit but give extra attention to the specified areas. Call out the focused findings in a separate subsection at the top of the report.
- Known issues: Verify whether each issue still exists in the current code. Report as "confirmed" or "already fixed".
Recommended WAVE paste format (tell the user if they choose WAVE):
Tip: Summarize the WAVE output rather than pasting raw — e.g.:
2 missing form labels, 1 broken skip link, 31 contrast errors, 2 skipped heading levels, 3 very small text
WAVE's numbered items (Very low contrast 1, 2, 3...) don't carry element info, so a summary works just as well.
3. Trace the Render Chain
Starting from the controller/handler, map every file that contributes to the final HTML output:
- Controller/Handler — the file handling the route (PHP controller, Express route handler, React page component, etc.)
- View/Template — the template file rendered by the controller (Blade, Twig, EJS, JSX/TSX, Vue SFC, Svelte, Handlebars, plain HTML, etc.)
- Includes/Components — header, footer, navigation, sidebar, modals, partials, shared components
- CSS files — all stylesheets loaded by the page:
- Linked stylesheets (
<link> tags)
- CSS modules or scoped styles
- Tailwind/utility classes (check
tailwind.config.js for custom values)
- CSS-in-JS (styled-components, emotion) — check the component files themselves
- JS files — all scripts loaded by the page that manipulate the DOM or handle interactions
Read each file. List the full render chain at the top of the report as the audit scope.
4. Structural Checks (Source Code Analysis)
Organized by WCAG principle. For each finding, report file:line, the WCAG criterion, and a fix suggestion.
Perceivable (1.x)
| Criterion |
Check |
| 1.1.1 Non-text Content |
Every <img> has a meaningful alt attribute. Flag filenames, UUIDs, or empty alt on non-decorative images. Decorative images should have alt="" AND aria-hidden="true". |
| 1.3.1 Info and Relationships |
Major page sections use semantic elements or landmark roles (<nav>, <main>, <aside>, <header>, <footer>). Filter sidebar has role="region" or equivalent. |
| 1.3.1 Info and Relationships |
Active navigation item has aria-current="page". |
| 1.3.1 Info and Relationships |
Heading hierarchy: h1 → h2 → h3 with no skipped levels. Exactly one <h1> per page. |
Operable (2.x)
| Criterion |
Check |
| 2.4.1 Bypass Blocks |
A skip-to-content link exists in the HTML (grep for skip-link or skip-to class in both HTML and CSS). Must be visible on focus. |
| 2.4.7 Focus Visible |
Search all CSS for outline: none or outline: 0 — each MUST have a replacement :focus-visible style nearby. |
| 2.4.7 Focus Visible |
Confirm :focus-visible is used instead of bare :focus for outline overrides. |
Understandable (3.x)
| Criterion |
Check |
| 3.1.1 Language of Page |
<html> tag has a correct lang attribute. |
| 3.1.2 Language of Parts |
If the page supports multiple languages: verify the lang attribute on <html> updates when the language changes. Check that language switcher navigation actually changes the page language, not just a UI variable. |
Robust (4.x)
| Criterion |
Check |
| 4.1.1 Parsing |
Collect ALL id="..." values across every file in the render chain. Flag any duplicates. |
| 4.1.2 Name, Role, Value |
Every <input>, <select>, <textarea> has a <label>, aria-label, or aria-labelledby. Placeholders alone do NOT count. |
| 4.1.2 Name, Role, Value |
Interactive elements that are not links use <button>, not <a href="#"> or <a href="javascript:">. |
| 4.1.2 Name, Role, Value |
Any role="option" element has a parent with role="listbox". |
| 4.1.2 Name, Role, Value |
title attributes are helpful context, not noise. Flag any title containing a raw URL or duplicating visible text exactly. |
| 4.1.2 Name, Role, Value |
Decorative elements (emoji spans, icon elements without text) have aria-hidden="true". |
| 4.1.2 Name, Role, Value |
Elements with aria-expanded also update their aria-label or have descriptive text that changes with state. |
| 4.1.3 Status Messages |
Dynamic content areas (AJAX loaders, live search results, notification areas) use aria-live regions. |
5. Visual / Contrast Flags (Requires Manual Verification)
Claude cannot compute rendered contrast ratios. Flag these patterns for manual verification with WAVE:
Color contrast indicators
- Any
color property using light grays (#666, #777, #888, #999, #aaa, #bbb, #ccc or equivalent rgb()/rgba()) on elements likely against white/light backgrounds
- White or light text (
#fff, #eee, #ddd) on bright/medium backgrounds (greens, oranges, yellows, light purples, light blues)
- Any
opacity value < 1 on text elements — this reduces effective contrast
Inherited colors
<a> tags inside styled containers that do NOT set an explicit color property — they inherit browser defaults which may fail contrast
- Pay special attention to: social icon links, mobile menu items, quick action links, footer links
Font sizes
- Any
font-size below 11px → flag as "very small text — likely fails WCAG"
- Text at 11-13px → flag as "small text — verify readability and contrast at this size"
Missing backgrounds
- Icon containers or badge elements that lack
background-color when sibling elements have one (inconsistency suggesting a missing style)
Dark mode contrast
- Check that
:focus-visible indicators have dark-mode variants
- Check that text colors in dark mode rules maintain readable contrast against dark backgrounds
- Check that
opacity on text doesn't reduce dark-mode contrast below usable levels
6. Mobile-Specific Checks
Mobile-only elements are invisible to desktop audits. This section catches them.
Find mobile-only elements: Search CSS for elements with display: none on desktop but visible inside @media queries with max-width breakpoints. Also search for elements only visible via @media (max-width: ...).
For every mobile-only element found, apply ALL checks from sections 4 and 5. Specifically:
- Mobile menu: links have explicit
color, focus styles work, aria-expanded toggles
- Mobile filter button: sufficient contrast, has accessible name
- Mobile overlays: focus trapping, close mechanism,
aria-modal
- Hamburger menu content: all interactive elements are keyboard accessible
Check mobile <a> tags have explicit color set (not relying on inheritance which may fail contrast on mobile backgrounds).
7. Output Report
Use this exact structure:
## WCAG 2.1 AA Audit — [Page Name]
### Render Chain
- Controller: [path]
- View: [path]
- Includes: [list]
- CSS: [list]
- JS: [list]
### WAVE Cross-Reference (only if WAVE context was provided)
For each issue from the WAVE summary:
1. **[WAVE issue description]** — STATUS: confirmed / already fixed
- Location: `file:line`
- Root cause: [explanation]
- Fix: [suggestion]
### Critical (WCAG AA Failures)
Issues that are definite WCAG 2.1 AA violations.
1. **[Criterion] [Short title]** — `file:line`
[Description and fix suggestion]
### Major (Should Fix)
Issues that are very likely failures or significant usability problems.
1. **[Criterion] [Short title]** — `file:line`
[Description and fix suggestion]
### Moderate (Recommended)
Issues that improve accessibility but may not be strict failures.
1. **[Criterion] [Short title]** — `file:line`
[Description and fix suggestion]
### Contrast Flags (Verify with WAVE)
Items that need manual contrast checking — cannot be computed from source.
1. **[Element description]** — `file:line`
`color: [value]` on `background: [value or "inherited"]` — [risk level]
### Mobile-Specific Issues
Issues only visible at mobile breakpoints.
1. **[Criterion] [Short title]** — `file:line`
[Description and fix suggestion]
### Passed Checks
Accessibility features that are correctly implemented.
- [Item that passed]
### Recommended Next Step
Run WAVE (https://wave.webaim.org/) on both desktop and mobile viewports to catch computed contrast failures that source-level auditing cannot detect.
Important Notes
- Be thorough: Read every file in the render chain. Don't skip includes or partials.
- Be precise: Always cite
file:line for findings.
- Don't guess contrast: Flag suspicious color combinations but mark them for manual verification.
- Check dark mode separately: Dark mode CSS often lives in different selectors or files.
- Mobile is a first-class audit target: Trace mobile-specific CSS and apply full checks to mobile-only elements.
- No false positives on decorative images:
alt="" is correct for decorative images — only flag it if the image conveys meaning.
1---2name: wcag3description: Audit a page for WCAG 2.1 AA accessibility issues. Traces the render chain, checks structural/semantic HTML, contrast indicators, ARIA, and outputs a prioritized report.4---56# WCAG 2.1 AA Audit Skill78Audit a page for WCAG 2.1 AA accessibility issues by tracing its render chain and checking source code.910## 1. Identify the Page1112Parse `$ARGUMENTS` for a page name or URL path:13- Page names: `homepage`, `about`, `contact`, `login`, `dashboard`, `product-detail`, etc.14- URL paths: `/`, `/about/`, `/products/123/`, `/blog/my-post/`, etc.1516Locate the **entry point** for the target page. Look for:17- **Route configuration**: e.g., `routes.php`, `web.php` (Laravel), `urls.py` (Django), route files in `src/routes/` or `app/router/`18- **Page registry or CMS config**: any database-driven or config-driven page lookup19- **Direct file mapping**: e.g., `pages/about.tsx` (Next.js), `src/views/About.vue` (Vue), `public_html/about.php`20- **Catch-all routers**: `router.php`, `index.php`, or framework entry points2122From the entry point, identify the **controller/handler**, **view/template**, and **includes/components** for the target page. If the argument is `homepage` or `/`, start from the root index file.2324## 2. Ask for External Input2526**Before doing any audit work**, use `AskUserQuestion` to ask:2728> Do you have external audit results (e.g., WAVE summary) to include?2930Options:31- **Yes — I'll paste WAVE results**: Wait for the user to paste their WAVE output. Then proceed.32- **Yes — other input**: Wait for the user to provide their context (focus areas, known issues, etc.). Then proceed.33- **No — run source-only audit**: Skip straight to the render chain trace and run all built-in checks.3435### How to use external input3637When the user provides input:38- **WAVE results**: Cross-reference each reported issue against the source code. For each WAVE finding, locate the exact `file:line`, confirm the issue, and propose a fix. Promote confirmed WAVE findings to **Critical** severity. Also run the full standard audit — WAVE may have missed structural issues.39- **Focus areas**: Run the full audit but give extra attention to the specified areas. Call out the focused findings in a separate subsection at the top of the report.40- **Known issues**: Verify whether each issue still exists in the current code. Report as "confirmed" or "already fixed".4142**Recommended WAVE paste format** (tell the user if they choose WAVE):43> Tip: Summarize the WAVE output rather than pasting raw — e.g.:44> `2 missing form labels, 1 broken skip link, 31 contrast errors, 2 skipped heading levels, 3 very small text`45> WAVE's numbered items (Very low contrast 1, 2, 3...) don't carry element info, so a summary works just as well.4647## 3. Trace the Render Chain4849Starting from the controller/handler, map **every file** that contributes to the final HTML output:50511. **Controller/Handler** — the file handling the route (PHP controller, Express route handler, React page component, etc.)522. **View/Template** — the template file rendered by the controller (Blade, Twig, EJS, JSX/TSX, Vue SFC, Svelte, Handlebars, plain HTML, etc.)533. **Includes/Components** — header, footer, navigation, sidebar, modals, partials, shared components544. **CSS files** — all stylesheets loaded by the page:55 - Linked stylesheets (`<link>` tags)56 - CSS modules or scoped styles57 - Tailwind/utility classes (check `tailwind.config.js` for custom values)58 - CSS-in-JS (styled-components, emotion) — check the component files themselves595. **JS files** — all scripts loaded by the page that manipulate the DOM or handle interactions6061Read each file. List the full render chain at the top of the report as the audit scope.6263## 4. Structural Checks (Source Code Analysis)6465Organized by WCAG principle. For each finding, report `file:line`, the WCAG criterion, and a fix suggestion.6667### Perceivable (1.x)6869| Criterion | Check |70|-----------|-------|71| 1.1.1 Non-text Content | Every `<img>` has a meaningful `alt` attribute. Flag filenames, UUIDs, or empty alt on non-decorative images. Decorative images should have `alt=""` AND `aria-hidden="true"`. |72| 1.3.1 Info and Relationships | Major page sections use semantic elements or landmark roles (`<nav>`, `<main>`, `<aside>`, `<header>`, `<footer>`). Filter sidebar has `role="region"` or equivalent. |73| 1.3.1 Info and Relationships | Active navigation item has `aria-current="page"`. |74| 1.3.1 Info and Relationships | Heading hierarchy: h1 → h2 → h3 with no skipped levels. Exactly one `<h1>` per page. |7576### Operable (2.x)7778| Criterion | Check |79|-----------|-------|80| 2.4.1 Bypass Blocks | A skip-to-content link exists in the HTML (grep for `skip-link` or `skip-to` class in both HTML and CSS). Must be visible on focus. |81| 2.4.7 Focus Visible | Search all CSS for `outline: none` or `outline: 0` — each MUST have a replacement `:focus-visible` style nearby. |82| 2.4.7 Focus Visible | Confirm `:focus-visible` is used instead of bare `:focus` for outline overrides. |8384### Understandable (3.x)8586| Criterion | Check |87|-----------|-------|88| 3.1.1 Language of Page | `<html>` tag has a correct `lang` attribute. |89| 3.1.2 Language of Parts | If the page supports multiple languages: verify the `lang` attribute on `<html>` updates when the language changes. Check that language switcher navigation actually changes the page language, not just a UI variable. |9091### Robust (4.x)9293| Criterion | Check |94|-----------|-------|95| 4.1.1 Parsing | Collect ALL `id="..."` values across every file in the render chain. Flag any duplicates. |96| 4.1.2 Name, Role, Value | Every `<input>`, `<select>`, `<textarea>` has a `<label>`, `aria-label`, or `aria-labelledby`. Placeholders alone do NOT count. |97| 4.1.2 Name, Role, Value | Interactive elements that are not links use `<button>`, not `<a href="#">` or `<a href="javascript:">`. |98| 4.1.2 Name, Role, Value | Any `role="option"` element has a parent with `role="listbox"`. |99| 4.1.2 Name, Role, Value | `title` attributes are helpful context, not noise. Flag any `title` containing a raw URL or duplicating visible text exactly. |100| 4.1.2 Name, Role, Value | Decorative elements (emoji spans, icon elements without text) have `aria-hidden="true"`. |101| 4.1.2 Name, Role, Value | Elements with `aria-expanded` also update their `aria-label` or have descriptive text that changes with state. |102| 4.1.3 Status Messages | Dynamic content areas (AJAX loaders, live search results, notification areas) use `aria-live` regions. |103104## 5. Visual / Contrast Flags (Requires Manual Verification)105106Claude cannot compute rendered contrast ratios. Flag these patterns for manual verification with WAVE:107108### Color contrast indicators109- Any `color` property using light grays (`#666`, `#777`, `#888`, `#999`, `#aaa`, `#bbb`, `#ccc` or equivalent `rgb()`/`rgba()`) on elements likely against white/light backgrounds110- White or light text (`#fff`, `#eee`, `#ddd`) on bright/medium backgrounds (greens, oranges, yellows, light purples, light blues)111- Any `opacity` value < 1 on text elements — this reduces effective contrast112113### Inherited colors114- `<a>` tags inside styled containers that do NOT set an explicit `color` property — they inherit browser defaults which may fail contrast115- Pay special attention to: social icon links, mobile menu items, quick action links, footer links116117### Font sizes118- Any `font-size` below 11px → flag as "very small text — likely fails WCAG"119- Text at 11-13px → flag as "small text — verify readability and contrast at this size"120121### Missing backgrounds122- Icon containers or badge elements that lack `background-color` when sibling elements have one (inconsistency suggesting a missing style)123124### Dark mode contrast125- Check that `:focus-visible` indicators have dark-mode variants126- Check that text colors in dark mode rules maintain readable contrast against dark backgrounds127- Check that `opacity` on text doesn't reduce dark-mode contrast below usable levels128129## 6. Mobile-Specific Checks130131Mobile-only elements are invisible to desktop audits. This section catches them.1321331. **Find mobile-only elements**: Search CSS for elements with `display: none` on desktop but visible inside `@media` queries with `max-width` breakpoints. Also search for elements only visible via `@media (max-width: ...)`.1341352. **For every mobile-only element found**, apply ALL checks from sections 4 and 5. Specifically:136 - Mobile menu: links have explicit `color`, focus styles work, `aria-expanded` toggles137 - Mobile filter button: sufficient contrast, has accessible name138 - Mobile overlays: focus trapping, close mechanism, `aria-modal`139 - Hamburger menu content: all interactive elements are keyboard accessible1401413. **Check mobile `<a>` tags** have explicit `color` set (not relying on inheritance which may fail contrast on mobile backgrounds).142143## 7. Output Report144145Use this exact structure:146147```148## WCAG 2.1 AA Audit — [Page Name]149150### Render Chain151- Controller: [path]152- View: [path]153- Includes: [list]154- CSS: [list]155- JS: [list]156157### WAVE Cross-Reference (only if WAVE context was provided)158For each issue from the WAVE summary:1591601. **[WAVE issue description]** — STATUS: confirmed / already fixed161 - Location: `file:line`162 - Root cause: [explanation]163 - Fix: [suggestion]164165### Critical (WCAG AA Failures)166Issues that are definite WCAG 2.1 AA violations.1671681. **[Criterion] [Short title]** — `file:line`169 [Description and fix suggestion]170171### Major (Should Fix)172Issues that are very likely failures or significant usability problems.1731741. **[Criterion] [Short title]** — `file:line`175 [Description and fix suggestion]176177### Moderate (Recommended)178Issues that improve accessibility but may not be strict failures.1791801. **[Criterion] [Short title]** — `file:line`181 [Description and fix suggestion]182183### Contrast Flags (Verify with WAVE)184Items that need manual contrast checking — cannot be computed from source.1851861. **[Element description]** — `file:line`187 `color: [value]` on `background: [value or "inherited"]` — [risk level]188189### Mobile-Specific Issues190Issues only visible at mobile breakpoints.1911921. **[Criterion] [Short title]** — `file:line`193 [Description and fix suggestion]194195### Passed Checks196Accessibility features that are correctly implemented.197198- [Item that passed]199200### Recommended Next Step201Run WAVE (https://wave.webaim.org/) on both desktop and mobile viewports to catch computed contrast failures that source-level auditing cannot detect.202```203204## Important Notes205206- **Be thorough**: Read every file in the render chain. Don't skip includes or partials.207- **Be precise**: Always cite `file:line` for findings.208- **Don't guess contrast**: Flag suspicious color combinations but mark them for manual verification.209- **Check dark mode separately**: Dark mode CSS often lives in different selectors or files.210- **Mobile is a first-class audit target**: Trace mobile-specific CSS and apply full checks to mobile-only elements.211- **No false positives on decorative images**: `alt=""` is correct for decorative images — only flag it if the image conveys meaning.