audit-accessibility
>
Works with
---
name: audit-accessibility
description: >
license: MIT
---
# Accessibility Audit
**Degree of freedom: MIXED** — Phases 0–1, 5 `[HIGH freedom]`; Phases 2–3
playwright/axe/Tab sequences `[LOW freedom — run exactly]`. Read
`protocol-browser-anti-stall` before any browser step.
## How to reason
1. **Observe** — quote axe rule+selector, heading list, or Tab snapshot
2. **Interpret** — can a keyboard or AT user complete the task?
3. **Classify** — WCAG fail / axe-false-positive / manual-only / correct-as-is
4. **Severity** — Level A = P0; Level AA = P1; best-practice = P2
## Worked example
> **Observe:** axe `label` on `/signup`; `<input name="email">` has no `id`/`aria-label`.
> **Interpret:** AT users cannot tell what to type (1.3.1 / 4.1.2).
> **Classify:** WCAG fail (not an axe false positive).
> **Severity:** P0 — Level A, blocks signup.
> **Finding:** `/signup` | 1.3.1+4.1.2 | P0 | unlabeled email | `<label for>`.
## Self-critique before reporting [LOW freedom — do not skip]
1. **WCAG SC + numbers** — not "contrast is bad"; quote ratio or selector
2. **Keyboard, not axe-only** — 2.1.1 fails even if axe is clean
3. **Every reachable page** — obscure routes still count
4. **Severity justified** — Level A = P0
5. **Axe is ~30–40%** — heading/focus/link-text/reading-order always manual
---
## Phase 0: Auto-Detect Pages and Components
### 0a. Discover Route Structure
```
Glob("**/app/**/page.{tsx,jsx,ts,js}")
Glob("**/pages/**/*.{tsx,jsx,ts,js}")
Glob("**/src/routes/**/*.{tsx,jsx,svelte,vue}")
```
### 0b. Find the App URL
```
Grep(pattern: "NEXT_PUBLIC_APP_URL|NEXT_PUBLIC_BASE_URL|VITE_APP_URL|APP_URL|localhost", glob: ".env*")
```
If no production URL, default to `http://localhost:3000` (or detected dev server port).
### 0c. Detect CSS Framework and Component Library
```
Grep(pattern: "tailwindcss|@chakra-ui|@mui|@radix-ui|shadcn|bootstrap|bulma", glob: "package.json")
```
### 0d. Detect Existing a11y Tooling
```
Grep(pattern: "axe-core|@axe-core|eslint-plugin-jsx-a11y|pa11y|lighthouse|@testing-library", glob: "package.json")
Grep(pattern: "aria-|role=|tabIndex|sr-only|visually-hidden", glob: "*.{tsx,jsx,vue,svelte}", output_mode: "count")
```
Record: `APP_URL`, `PAGES`, `CSS_FRAMEWORK`, `COMPONENT_LIB`, `EXISTING_A11Y_TOOLS`, `ARIA_USAGE_COUNT`.
---
## Phase 1: Research Current WCAG Guidelines
### 1a. Firecrawl Research
```json
firecrawl:firecrawl_search
{
"query": "WCAG 2.2 success criteria checklist web accessibility 2026",
"limit": 5
}
```
```json
firecrawl:firecrawl_search
{
"query": "axe-core automated accessibility testing best practices common false positives",
"limit": 5
}
```
```json
firecrawl:firecrawl_search
{
"query": "<CSS_FRAMEWORK> <COMPONENT_LIB> accessibility best practices common issues",
"limit": 5
}
```
Scrape 2–3 authoritative results:
```json
firecrawl:firecrawl_scrape
{
"url": "<BEST_RESULT_URL>",
"formats": ["markdown"],
"onlyMainContent": true
}
```
### 1b. Framework-Specific a11y Docs (Context7)
```json
context7:resolve-library-id
{
"libraryName": "<COMPONENT_LIB e.g. radix-ui or chakra-ui>"
}
```
```json
context7:query-docs
{
"libraryId": "<RESOLVED_ID>",
"query": "accessibility ARIA keyboard navigation focus management"
}
```
---
## Phase 2: Automated Scanning (axe-core via Playwright)
### 2a. Navigate to Each Page
For each page discovered in Phase 0a:
```bash
PW="npx --yes @playwright/cli@latest"
$PW -s=a11y open --headed "<APP_URL><ROUTE>" # first page; use `goto` for subsequent ones
```
Apply the `protocol-browser-anti-stall` protocol: wait 2s, snapshot, verify page loaded.
### 2b. Inject and Run axe-core
Use `run-code` to inject axe-core from CDN and run a full scan:
```bash
$PW -s=a11y run-code 'async (page) => { await page.addScriptTag({ url: "https://cdnjs.cloudflare.com/ajax/libs/axe-core/4.10.2/axe.min.js" }); return await page.evaluate(async () => { const r = await axe.run(); return { violations: r.violations.map(v => ({ id: v.id, impact: v.impact, description: v.description, help: v.help, helpUrl: v.helpUrl, nodes: v.nodes.length, targets: v.nodes.slice(0, 3).map(n => n.target[0]) })), passes: r.passes.length, incomplete: r.incomplete.length, inapplicable: r.inapplicable.length }; }); }'
```
For each violation returned, record:
- **Rule ID** (e.g., `color-contrast`, `image-alt`, `label`)
- **Impact** (critical, serious, moderate, minor)
- **WCAG criteria** mapped from the rule
- **Affected elements** (CSS selectors)
- **Count** of affected nodes
### 2c. Check Specific WCAG Categories Manually
**Heading Hierarchy (WCAG 1.3.1):**
```bash
$PW -s=a11y eval '() => Array.from(document.querySelectorAll("h1,h2,h3,h4,h5,h6")).map(h => ({ tag: h.tagName, text: h.textContent.trim().substring(0, 50) }))'
```
Verify: only one `h1`, headings don't skip levels (h1 -> h3 without h2).
**Image Alt Text (WCAG 1.1.1):**
```bash
$PW -s=a11y eval '() => Array.from(document.querySelectorAll("img")).map(img => ({ src: img.src.split("/").pop(), alt: img.alt, hasAlt: img.hasAttribute("alt"), decorative: img.getAttribute("role") === "presentation" || img.alt === "" }))'
```
Verify: all informative images have descriptive alt text, decorative images have `alt=""` or `role="presentation"`.
**Link Text (WCAG 2.4.4):**
```bash
$PW -s=a11y eval '() => Array.from(document.querySelectorAll("a")).filter(a => ["click here", "here", "read more", "learn more", "", "link"].includes((a.textContent || "").trim().toLowerCase())).map(a => ({ href: a.href, text: (a.textContent || "").trim(), ariaLabel: a.getAttribute("aria-label") }))'
```
Flag generic link text ("click here", "read more", empty links without aria-label).
**Form Labels (WCAG 1.3.1, 4.1.2):**
```bash
$PW -s=a11y eval '() => Array.from(document.querySelectorAll("input,select,textarea")).map(el => ({ type: el.type, name: el.name, id: el.id, hasLabel: !!el.id && !!document.querySelector(`label[for="${el.id}"]`), ariaLabel: el.getAttribute("aria-label"), ariaLabelledBy: el.getAttribute("aria-labelledby"), placeholder: el.placeholder }))'
```
Verify: every form control has an associated `<label>`, `aria-label`, or `aria-labelledby`.
**Language Attribute (WCAG 3.1.1):**
```bash
$PW -s=a11y eval '() => ({ htmlLang: document.documentElement.lang, htmlDir: document.documentElement.dir })'
```
Verify: `<html>` has a valid `lang` attribute.
### 2d. Take Evidence Screenshots
For each page, capture a screenshot as visual evidence:
```bash
$PW -s=a11y screenshot --filename ".playwright-mcp/a11y-<route>.png"
```
---
## Phase 3: Keyboard Navigation Testing
### 3a. Tab Order Test
For each page, test that all interactive elements are reachable via Tab key:
```bash
PW="npx --yes @playwright/cli@latest"
$PW -s=a11y open --headed "<APP_URL><ROUTE>" # first page; use `goto` for subsequent ones
```
Press Tab repeatedly and snapshot after each press to track focus movement:
```bash
$PW -s=a11y press Tab
$PW -s=a11y snapshot
```
After each Tab, check:
- [ ] Focus is visible (there is a focus indicator on the active element)
- [ ] Focus order is logical (follows visual reading order)
- [ ] No focus trap (Tab eventually cycles back to the beginning or reaches the end)
- [ ] Skip links exist (if there is a large navigation area)
Repeat Tab at least 10-15 times per page to cover the main interactive elements.
### 3b. Interactive Element Testing
For each interactive component (dropdowns, modals, tabs, accordions):
**Enter/Space activation (WCAG 2.1.1):**
- Tab to the element
- Press Enter or Space
- Verify the action fires (snapshot to confirm state change)
**Escape to close (WCAG 2.1.1 for modals/dialogs):**
```bash
$PW -s=a11y press Escape
```
Verify: modals/dropdowns close, focus returns to the trigger element.
**Arrow key navigation (for tab panels, menus, listboxes):**
```bash
$PW -s=a11y press ArrowDown
```
### 3c. Focus Management After State Changes
Test that focus is managed correctly after dynamic updates:
- After opening a modal: focus should move into the modal
- After closing a modal: focus should return to the trigger
- After deleting an item from a list: focus should move to the next item or a logical location
- After form submission: focus should move to a success/error message
---
## Phase 4: Cross-Check with Sentry
### 4a. Search for Assistive Technology Errors
```json
sentry:search_issues
{
"organizationSlug": "<ORG_SLUG>",
"projectSlug": "<PROJECT_SLUG>",
"query": "is:unresolved aria OR screenreader OR voiceover OR talkback OR nvda OR jaws OR focus OR tabindex OR keyboard"
}
```
### 4b. Check for JavaScript Errors on Keyboard Interaction
```json
sentry:search_issues
{
"organizationSlug": "<ORG_SLUG>",
"projectSlug": "<PROJECT_SLUG>",
"query": "is:unresolved keydown OR keyup OR keypress OR focusin OR focusout"
}
```
### 4c. Browser/Device Breakdown
If errors cluster on AT-favored browsers:
```json
sentry:get_issue_tag_values
{
"organizationSlug": "<ORG_SLUG>",
"issueId": "<ISSUE_ID>",
"tagKey": "browser"
}
```
---
## Phase 5: Static Code Analysis
### 5a. Missing ARIA Attributes
```
Grep(pattern: "onClick(?!.*role=)(?!.*tabIndex)", glob: "*.{tsx,jsx}")
```
Flag `div` or `span` elements with click handlers but no `role` or `tabIndex` — these are invisible to keyboard and screen reader users.
### 5b. Inaccessible Custom Components
```
SemanticSearch(query: "Where are custom interactive components defined (dropdowns, modals, tabs, accordions) that don't use a component library?", target_directories: [])
```
Check each custom interactive for:
- Proper ARIA roles (`role="dialog"`, `role="tabpanel"`, etc.)
- Keyboard event handlers (`onKeyDown`)
- Focus management (`useRef` + `.focus()`)
- ARIA states (`aria-expanded`, `aria-selected`, `aria-hidden`)
### 5c. Color Contrast in CSS/Tailwind
```
Grep(pattern: "text-gray-[345]|text-slate-[345]|text-zinc-[345]|text-neutral-[345]|color:\\s*#[789abc]", glob: "*.{tsx,jsx,css,scss}")
```
Light gray text on white background is the most common contrast failure. Flag files using light gray text colors for manual contrast checking.
---
## Phase 6: Report
```
═══════════════════════════════════════════════════════
WCAG 2.2 ACCESSIBILITY AUDIT REPORT
Project: <PROJECT_NAME>
Date: <DATE>
Standard: WCAG 2.2 Level AA
Pages Tested: <COUNT>
═══════════════════════════════════════════════════════
## 1. EXECUTIVE SUMMARY
| Metric | Result |
|---------------------------|-----------------|
| Pages tested | X |
| Total violations | N |
| Critical violations | N |
| Serious violations | N |
| axe-core rules passed | N |
| Keyboard navigable pages | X/Y |
| Overall compliance | X% (estimated) |
## 2. AXE-CORE SCAN RESULTS (per page)
| Page | Critical | Serious | Moderate | Minor | Pass | Score |
|------|----------|---------|----------|-------|------|-------|
| / | ... | ... | ... | ... | ... | A-F |
| /about | ... | ... | ... | ... | ... | A-F |
## 3. VIOLATIONS BY WCAG CRITERION
| WCAG SC | Name | Level | Pages Affected | Elements | Impact | Status |
|---------|------|-------|----------------|----------|--------|--------|
| 1.1.1 | Non-text Content | A | ... | ... | ... | PASS/FAIL |
| 1.3.1 | Info and Relationships | A | ... | ... | ... | PASS/FAIL |
| 1.4.3 | Contrast (Minimum) | AA | ... | ... | ... | PASS/FAIL |
| 2.1.1 | Keyboard | A | ... | ... | ... | PASS/FAIL |
| 2.4.3 | Focus Order | A | ... | ... | ... | PASS/FAIL |
| 2.4.4 | Link Purpose | A | ... | ... | ... | PASS/FAIL |
| 2.4.7 | Focus Visible | AA | ... | ... | ... | PASS/FAIL |
| 3.1.1 | Language of Page | A | ... | ... | ... | PASS/FAIL |
| 4.1.2 | Name, Role, Value | A | ... | ... | ... | PASS/FAIL |
## 4. KEYBOARD NAVIGATION
| Page | Tab Order Logical | Focus Visible | No Focus Trap | Skip Link | Score |
|------|-------------------|---------------|---------------|-----------|-------|
| / | ... | ... | ... | ... | A-F |
## 5. INTERACTIVE COMPONENTS
| Component | Location | ARIA Roles | Keyboard Operable | Focus Managed | Score |
|-----------|----------|------------|-------------------|---------------|-------|
| Modal | ... | ... | ... | ... | A-F |
| Dropdown | ... | ... | ... | ... | A-F |
## 6. SENTRY ASSISTIVE TECHNOLOGY ERRORS
| Issue | Error Type | Events | Browser/AT | Fix Needed |
|-------|------------|--------|------------|------------|
| ... | ... | ... | ... | ... |
## 7. STATIC CODE FINDINGS
| Pattern | Files | Count | Risk |
|---------|-------|-------|------|
| onClick without role/tabIndex | ... | N | High |
| Custom interactive without ARIA | ... | N | High |
| Low-contrast text classes | ... | N | Medium |
## 8. CRITICAL FINDINGS (Action Required)
P0 — Level A violations (legal risk):
1. [finding with WCAG SC, element, page, evidence]
P1 — Level AA violations (compliance gap):
1. [finding with WCAG SC, element, page, evidence]
P2 — Best practice improvements:
1. [finding with rationale]
## 9. RECOMMENDATIONS
| # | WCAG SC | Current | Recommended | Effort | Files to Change |
|---|---------|---------|-------------|--------|-----------------|
| 1 | ... | ... | ... | S/M/L | ... |
═══════════════════════════════════════════════════════
```More Accessibility skills
skill-creator
anthropics/skills
Create new skills, modify and improve existing skills, and measure skill performance. Use when users want to create a skill from scratch, edit, or optimize an existing skill, run evals to test a skill, benchmark skill performance with variance analysis, or optimize a skill's description for better triggering accuracy.
hyperframes-core
heygen-com/hyperframes
The HyperFrames composition contract — build one renderable project. Use for composition structure, the `data-*` timing attributes, `class="clip"`, tracks, sub-compositions, variables, framework-owned media playback, deterministic-render rules, and validation. Also covers Tailwind projects and the STORYBOARD.md / SCRIPT.md plan formats. Read before writing composition HTML.
ui-ux-pro-max
nextlevelbuilder/ui-ux-pro-max-skill
UI/UX design intelligence for web, mobile, and desktop. This skill should be used when designing, building, reviewing, or fixing interfaces, including pages, components, design systems, accessibility, interaction, responsive layout, typography, color, charts, and stack-specific UI implementation. Searchable local data: 79 searchable styles (50 active), 192 product palettes and reasoning profiles, 74 font pairings, 119 UX guidelines, 105 icons, 17 GSAP presets, 25 chart types, and 22 stacks.

