Scan accessibility
Skill web-DnA/navable-web-accessibility-skills/skills/scan-accessibility
Agent skills that teach AI coding agents to scan, fix, audit, and review WCAG 2.1 Level A + AA accessibility issues using the navable MCP server.
npx -y skills add web-DnA/navable-web-accessibility-skills --skill scan-accessibilityAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Scans a URL for WCAG 2.1 AA accessibility violations using Playwright and axe-core via the navable MCP server. Generates a prioritized fix plan with before/after code patterns and EN 301 549 mapping. Use this as the default skill for accessibility work: when the user asks to check, scan, or fix accessibility of a page or URL. (For structure-only audits, see audit-page-structure.)
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
16.2 KB, as published. Nobody here has run it
Accessibility Scan & Fix Workflow
Prerequisites
- navable MCP server must be configured (see Step 0)
- Playwright Chromium browser engine — installed automatically when
@navable/mcpis installed. If the auto-download was skipped (restricted network or CI), runnpx playwright install chromiummanually. A missing browser will return a clear error with install instructions. - Target URL accessible on localhost (any port). External hosts require a
.navable.jsonconfig withallowedHosts.
Workflow
Step 0: Ensure navable MCP Server is Configured
Check if the navable MCP server is already available by looking for an existing config:
- VS Code (Copilot) — check if
.vscode/mcp.jsonexists and contains a"navable"entry - Cursor — check if
.cursor/mcp.jsonexists and contains a"navable"entry - Claude Code — check if
navableappears in the MCP server list
If the navable MCP server is not configured, create the config file:
For VS Code — create or update .vscode/mcp.json:
{
"servers": {
"navable": {
"command": "npx",
"args": ["-y", "@navable/mcp"]
}
}
}
For Cursor — create or update .cursor/mcp.json:
{
"mcpServers": {
"navable": {
"command": "npx",
"args": ["-y", "@navable/mcp"]
}
}
}
For Claude Code — run in terminal:
claude mcp add navable -- npx -y @navable/mcp
After creating the config, the IDE will auto-detect and start the MCP server. The @navable/mcp
package is downloaded automatically via npx on first use — no separate install step needed.
If .navable-plan.json is written to the wrong directory, add the NAVABLE_PROJECT_ROOT env var to
the config pointing to the project root.
Once the MCP server is available, proceed to Step 1.
Step 1: Check for Existing Plan (Resumability)
Check if .navable-plan.json exists in the project root.
- If it exists with pending items → skip to Step 4 (resume fixing)
- If it exists with all items done → ask user if they want to re-scan or verify
- If it doesn't exist → proceed to Step 2
Step 2: Scan the URL
Call run_accessibility_scan with the user's URL:
run_accessibility_scan({ url: "<user-provided URL>" })
The URL can be any localhost address (e.g. http://localhost:3000,
http://localhost:4200/checkout, http://127.0.0.1:8080).
Optional parameters:
tags— axe-core rule tags to run (default:wcag2a, wcag21a, wcag2aa, wcag21aa)include— CSS selectors to scope the scan to specific regionsexclude— CSS selectors to skip (e.g. third-party widgets)engines— Engines to run. Default:["axe"]. Pass["axe", "htmlcs"]for crossover validation (Pa11y/HTMLCS as a second engine). Adds ~2–4 s wall-clock per scan and grows result size by ~30–70%. Use only when the user explicitly asks for higher confidence, a compliance audit, or BFSG/EN 301 549 sign-off. For iterative fix loops, keep the default["axe"].
The result includes a scanId field — save it for Step 3.
Note on large results: Scan output is typically 10-20 KB. Some agents write large tool results
to temp files instead of displaying them inline. If the result is in a temp file, read it to extract
the scanId value. You do not need to pass the full scan object to the next step.
Step 3: Generate Fix Plan
Use the scanId from Step 2 (preferred — avoids re-serializing large data):
generate_fix_plan({ scanId: "<scanId from step 2>" })
Fallback (only if the server was restarted and scanId is no longer available):
generate_fix_plan({ scan: <full_scan_output> })
This writes .navable-plan.json to the project root. The response includes planPath — the
absolute path where the file was written.
If planPath points to the wrong location, set NAVABLE_PROJECT_ROOT in the MCP server config.
The plan is pre-sorted by priority:
- critical
- serious
- moderate
- minor
Do NOT reorder the plan. The server applies the correct priority sort.
Step 3b: Confirm with User Before Fixing
STOP. Do not proceed to Step 4 automatically.
Present the plan summary to the user and ask for explicit confirmation:
- State clearly: "Scanned against WCAG 2.1 Level A + AA (50 criteria)."
- Show the
summaryfrom thegenerate_fix_planresponse (total items, critical/serious counts) - List the
topItemsso the user can see what will be changed - Ask: "Do you want me to apply these fixes now?"
Only proceed to Step 4 if the user confirms. If they decline or ask to review first, stop and wait.
This is a hard stop — never auto-apply fixes after generating the plan.
Step 4: Fix Items in Priority Order
Work through plan.items where status === "pending":
Group fixes by DOM element first
Multiple plan items often target the same element — either as separate axe nodes in a multi-node violation, or as cross-engine flags under different WCAG criteria. Editing the same element once for all its findings avoids regressions (a later fix undoing an earlier one) and cuts file reads/writes.
Two items belong in the same element bucket when both:
- Selectors match — identical, or one selector is a tail of the other (the longer one
has
>immediately before where the shorter one starts; the shorter side need not contain>itself, soimg:nth-child(1)matches… > img:nth-child(1)), or they match after stripping[attribute]filters (e.g.button[type="button"]matchesbutton). When you fall back to the attribute-stripped path, require exact HTML equality — distinct elements likeinput[type="checkbox"]andinput[type="radio"]collapse to the same stripped selector, so any HTML divergence means they are different elements. affectedNodes[0].htmlsnippets match — normalize whitespace (collapse runs of spaces, trim) and lowercase, then compare the first ~200 characters. The 200-char window is wide enough to catch divergence in child content (e.g.<option>text inside two different<select>elements that share their opening tag).
If selectors look similar but the HTML differs, treat them as different elements. Two
<select>elements in two different forms can share the suffixform > div:nth-child(4) > selectbut are distinct — their<option>content disambiguates them, but only if you compare enough of the HTML.
:nth-childindex drift. axe and HTMLCS occasionally disagree on:nth-childindices when there are sibling text nodes or comments. If two findings are clearly the same element but the indices differ by one, trust the HTML snippet over the selector and bucket them together.
Process per bucket:
- Read all fix items in the bucket and list every WCAG SC they cover.
- Design one minimal HTML edit that addresses every fix in the bucket.
- Apply the edit.
- Call
update_fix_statuswith all resolved fix IDs in one call.
Example. A <select> flagged by 5 fixes (SC 3.3.2 missing label, SC 4.1.2 no name, SC 1.3.1
no programmatic label, SC 4.1.2 no value, SC 1.3.1 no <optgroup>) is one edit:
- <select>
- <option>Choose your team size</option>
- <option>1-10</option>
- <option>11-50</option>
- </select>
+ <label for="team-size">Team size</label>
+ <select id="team-size" name="teamSize">
+ <option value="" disabled selected>Choose your team size</option>
+ <optgroup label="Sizes">
+ <option value="1-10">1–10</option>
+ <option value="11-50">11–50</option>
+ </optgroup>
+ </select>
Then: update_fix_status({ fixIds: ["fix-5", "fix-16", "fix-17", "fix-31", "fix-32"], status: "done" }).
For each bucket (or single-item bucket)
- Identify the violation category from the
ruleIdand load the relevant fix guide - Locate the source file — use
item.affectedNodes[].selectoranditem.affectedNodes[].htmlto find the component - Apply the fix following the before/after pattern from the fix guide
- Mark it done — call
update_fix_status({ fixId: "fix-1", status: "done" })to update the plan file. This is faster and safer than editing.navable-plan.jsonmanually. - Move to the next pending item
Progress checklist (update as you go):
- Fix critical items
- Fix serious items
- Fix moderate items
- Fix minor items
Step 5: Verify Fixes
Re-scan the URL with run_accessibility_scan using the same parameters as Step 2.
- If violations remain → report them and offer to continue fixing
- If clean → confirm all issues resolved
- Update
plan.verificationin.navable-plan.json:{ "completedAt": "<ISO 8601>", "remainingViolations": 0, "passed": true }
Always add this note:
Automated scanning cannot detect every accessibility issue. Manual testing is still required for keyboard navigation flows, screen reader behavior, and cognitive load assessment.
Fix Guides by Category
Load the relevant guide when fixing a specific violation type:
| Category | Guide | Rules |
|---|---|---|
| Images & media | references/fix-guide-images.md | image-alt, svg-img-alt, object-alt, input-image-alt, role-img-alt, area-alt, video-caption |
| Forms & labels | references/fix-guide-forms.md | label, select-name, autocomplete-valid, input-button-name |
| Color & contrast | references/fix-guide-color.md | color-contrast, color-contrast-enhanced, link-in-text-block, css-orientation-lock |
| Navigation & links | references/fix-guide-navigation.md | link-name, bypass, frame-title, button-name, label-content-name-mismatch, meta-viewport |
| Headings & structure | references/fix-guide-headings.md | heading-order, page-has-heading-one, document-title, empty-heading, p-as-heading |
| Landmarks & regions | references/fix-guide-landmarks.md | region, landmark-one-main |
| ARIA usage | references/fix-guide-aria.md | aria-required-attr, aria-valid-attr-value, aria-allowed-attr, aria-hidden-focus, aria-allowed-role, aria-required-children, aria-required-parent, aria-roles, aria-valid-attr, duplicate-id, duplicate-id-aria |
| Keyboard & focus | references/fix-guide-keyboard.md | tabindex, focus-order-semantics, keyboard, target-size, accesskeys, no-autoplay-audio, scrollable-region-focusable |
| Language attributes | references/fix-guide-language.md | html-has-lang, html-lang-valid, valid-lang |
| Tables & lists | references/fix-guide-tables.md | td-has-header, th-has-data-cells, table-fake-caption, definition-list, dlitem, list, listitem |
MCP Resources (load on demand)
navable://docs/fix-patterns/{ruleIds}— preferred: before/after code for specific rules (e.g.navable://docs/fix-patterns/image-alt,label,color-contrast). Pass comma-separated rule IDs from the scan.navable://docs/fix-patterns— all 55 rules (~49 KB). Only load if you need the full reference.navable://docs/aria-patterns/{patternSlug}— single ARIA widget pattern detailnavable://docs/aria-patterns— index of 25 patterns (compact list)navable://docs/semantic-html/{element}— single element detailnavable://docs/semantic-html— index of all elements (compact list)navable://docs/wcag-mapping— WCAG 2.1 AA mapping table (compact)navable://docs/bfsg-legal— BFSG legal context, glossary, enforcement (optional, for German compliance)
.navable-plan.json Format Reference
{
"planId": "navable-plan-<timestamp>",
"url": "http://localhost:3000/...",
"items": [
{
"id": "fix-1",
"ruleId": "image-alt",
"impact": "critical",
"priority": 1,
"wcagSc": ["1.1.1"],
"en301549": "9.1.1.1",
"help": "Images must have alternate text",
"affectedNodes": [
{ "selector": "img.hero", "html": "<img src=\"...\">", "failureSummary": "..." }
],
"fixDescription": "...",
"status": "pending",
"appliedAt": null
}
],
"manualReview": [],
"verification": null
}
Gotchas
- Always use
scanIdfor chaining. Afterrun_accessibility_scan, passscanId(not the full scan object) togenerate_fix_plan. This avoids serialization issues with large payloads. run_accessibility_scanrequires a running server. The URL must be reachable. If it fails, ask the user to confirm their dev server is running.- CSS selectors may not map 1:1 to source files. Use
htmlsnippets fromaffectedNodesto locate components. Search for unique strings (class names, text content, attributes) in the codebase. - The plan is sorted server-side. Do not re-sort items by your own logic. Work through them in array order.
- Use
update_fix_statusafter each fix, not manual file edits. This ensures correct JSON formatting and enables resumability if the session is interrupted. incompleteitems in the scan are issues axe cannot determine automatically. They appear inplan.manualReview. Mention them to the user but do not auto-fix them.- Dual-engine results (when
engines: ["axe", "htmlcs"]): violations include asourcefield ("axe"or"htmlcs"). An entry withalsoFlaggedBy: ["htmlcs"]is double-confirmed by both engines — treat as high confidence and prioritize. HTMLCS-only entries (source: "htmlcs") come with ahelpUrl(WCAG Understanding doc) anddeveloperNote(one-sentence guidance) to use in place of fix-pattern resources, since HTMLCS rule IDs don't matchnavable://docs/fix-patterns/*. - Framework detection: Check
package.jsonfor react, vue, svelte, angular to choose the right fix patterns from the guides.
Gives 0 of the 12 instructions most mcp tooling skills give
Counted across 638 of the 750 authors here whose files we hold, read 2026-08-06
- create ten complex read-only evaluation questionsin 71 of 638, across 17 files
- test servers using MCP Inspectorin 60 of 638, across 18 files
- provide actionable error messagesin 56 of 638, across 14 files
- prioritize comprehensive API coverage over specific workflowsin 54 of 638, across 12 files
- use TypeScript and Streamable HTTP for remote serversin 53 of 638, across 7 files
- define structured output schemas where possiblein 51 of 638, across 9 files
- use Zod or Pydantic for input schemasin 48 of 638, across 6 files
- fetch MCP specification pages with markdown suffixin 46 of 638, across 4 files
- load framework documentation using WebFetchin 45 of 638, across 3 files
- verify each evaluation answer independentlyin 45 of 638, across 3 files
- implement API client with authentication and paginationin 45 of 638, across 3 files
- Define input schemas with validationin 28 of 638, across 10 files
Said here and by no other author read
- check if the navable server is configured
- resume pending items if a plan file exists
- run the accessibility scan
- generate a fix plan from the scan
- group violations by DOM element
- fix items in priority order
Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once.