a11y

Accessibility review for Vue surfaces — the code-cleaning analog of /code-review and /simplify, pointed at WCAG/ARIA. Reviews just your diff (or a package/file), rates findings by severity, and either

SKILL.md

GitHub

A11y Skill — the steering brain

You drive an accessibility review. The actual analysis lives in the a11y subagent (.claude/agents/a11y.md) — this skill decides what scope and how deep, spawns the agent (fanning out for 5+ files), collates findings into one severity-rated report, and then offers to comment them on the PR or fix the safe ones. Keep the analysis logic in the agent; keep orchestration here.

This is the accessibility member of the code-cleaning family. It deliberately mirrors .claude/skills/review/SKILL.md (diff-scoped, 3-level severity, the same report format, an offer-to-fix step) and the red-team skill/agent split (depth ladder, spawn-and-collate) — just for a11y instead of bugs or exploits.

Why a separate skill (not folded into /review)

a11y has its own engines (eslint-plugin-vuejs-accessibility, @axe-core/playwright), its own severity source (axe critical/serious/moderate/minor), and its own --fix shape (add alt/aria-label/label-for/role+tabindex). A dedicated, composable skill keeps it clean — exactly like red-team sitting beside /review.

1. Resolve { depth, scope, comment, fix } from the request

Scope (mirrors the review skill's modes):

Invocation What it reviews scope passed to agent
/a11y All uncommitted .vue changes (staged + unstaged) "diff"
/a11y last N .vue files in the last N commits "diff" (vs HEAD~N)
/a11y --scope <pkg> A whole package's .vue packages/<pkg> (or apps/<pkg>)
/a11y --file <path> A single .vue file that path

Depth (mirrors red-team's ladder):

The user said… depth
/a11y (bare), a PR/diff context, quick quick (static, diff-scoped)
/a11y --scope <pkg> / standard standard (full static sweep of the scope)
/a11y deep / "boot it" / "run axe" deep (static + @axe-core/playwright on a fixture)

Defaults: a bare /a11y on a working tree → quick + "diff". A --scope/--file with no depth → standard. Only go deep when explicitly asked (it boots a fixture).

Flags: --comment → post findings as inline PR comments. --fix → apply safe remediations to the working tree. Both can combine with any scope/depth.

2. Severity map (axe-aligned — reuse the review skill's 3 levels + format)

Level a11y meaning Sources
🔴 Critical An AT user is blocked — can't perceive or operate it axe critical/serious; missing alt on a meaningful image; an unlabeled control; a keyboard-unreachable interactive element; an input with no label
🟡 Warning Degrades the experience; a workaround exists axe moderate; redundant/misapplied role; positive tabindex; heading-order break; missing media caption; autofocus
🔵 Note Polish axe minor; decorative-image alt nit; accesskey; cosmetic ARIA redundancy

Same emoji + report format as /review, so a mixed code-cleaning pass reads consistently.

3. Gather the scope

Uncommitted (/a11y, --scope, --file):

git diff --name-only --diff-filter=d | grep -E '\.vue$'          # unstaged
git diff --cached --name-only --diff-filter=d | grep -E '\.vue$' # staged

Recent commits (/a11y last N):

git diff --name-only HEAD~N..HEAD | grep -E '\.vue$'

If --scope is set, Glob packages/<pkg>/**/*.vue (or apps/<pkg>/**/*.vue). If the diff has no .vue files, say so and stop — a11y only reviews templates.

Note: apps/, docs/, pocs/ are excluded from the root eslint config, so for an app scope the agent lints from inside the app / by file. It handles this; just pass the path.

4. Spawn the subagent

Spawn a11y via the Agent tool with { scope, depth, fix } in the prompt.

  • < 5 files → one subagent (or just do the static pass inline if it's a single small file — agent overhead isn't worth it for one --file).
  • 5+ files → fan out: split the files into groups (by directory — components/, pages/, layouts/), spawn one a11y subagent per group with its file subset as scope, and launch them in parallel (multiple Agent calls in one message). Same parallelization rule as /review. The agents are synchronous — you hold their results when they return; never report "running in the background".

Pass fix through only when the user asked for --fix (so a plain review run never touches files).

5. Collate & report

Merge the agents' findings, de-dupe anything the same rule flagged twice, and print the review skill's report format:

## A11y Review Summary

| Severity | Count |
|----------|-------|
| 🔴 Critical | X |
| 🟡 Warning | Y |
| 🔵 Note | Z |

### 🔴 Critical

#### [Short title]
**File:** `path/to/file.vue:42`
**Rule:** `vuejs-accessibility/alt-text` (or axe rule id)
**Issue:** [Who it hurts and how — "screen-reader users get no description of this image"]
**Fix:** [The exact attribute/element to add]

--- (repeat for 🟡 / 🔵)

If nothing found:

## A11y Review: clean ✅
Reviewed N templates, no accessibility issues found.

Mark each finding confirmed (an engine flagged it) vs suspected (smell only), mirroring red-team — don't present a reasoned smell as a confirmed violation.

6. --comment — inline PR comments

When --comment is set and the work is on a PR, post each finding as an inline review comment on the offending file:line, dovetailing with the existing 🎯 Preview feedback machinery (the reviewer sees a11y notes pinned to the exact markup, same as design feedback):

  1. mcp__github__pull_request_read to resolve the PR for the current branch and its head SHA.
  2. For each finding, mcp__github__add_comment_to_pending_review with the file path and line, body = the finding (lead with the 🔴/🟡/🔵 + rule, then Issue/Fix). Prefix the body with a ♿ A11y tag so it's distinguishable from 🎯 Preview feedback and code-review comments.
  3. mcp__github__pull_request_review_write to submit the pending review as COMMENT (never auto-REQUEST_CHANGES / APPROVE).

If there's no PR (local-only), skip inline comments and just print the report — say so rather than erroring.

7. --fix — apply safe remediations

When --fix is set, the agent applies (or you apply, for a small inline run) only the safe, mechanical fixes — the same set the agent doc lists:

  • Missing alt → alt="" for decorative, a derived short alt for meaningful (leave genuinely ambiguous ones for the human).
  • Icon-only control → aria-label describing the action.
  • Input without a label → wrap in UFormField label / add <label for> / aria-label.
  • Mouse-only interactive element → add role + tabindex="0" + a keyboard handler mirroring @click (preferred: convert <div @click> → <button> when drop-in).
  • Positive tabindex → tabindex="0".
  • Redundant role → remove it.

Then pnpm typecheck the affected app(s)/package — never leave the tree red:

pnpm -r --filter './apps/*' typecheck   # or the specific package's typecheck

Anything needing real judgment (image meaning, heading restructure, colour contrast, reworking an interaction) is reported, not auto-fixed.

8. Offer to fix (when not already --fix)

After printing the report (and any --comments), close with the review skill's offer:

Want me to fix any of these? (all / critical only / pick by number)

If the user says "all" or "critical only", apply the safe remediations directly — no per-fix confirmation — then pnpm typecheck. Findings that aren't in the safe set get called out as "needs a human decision" with the reason.

Relationship to the other entry points

  • eslint-a11y (eslint.config.mjs, #726/#727) runs the rules warn-first in pnpm lint — it's the always-on baseline. /a11y is the on-demand, diff-scoped, severity-rated, fixable layer on top (the rules are one of its engines).
  • It sits beside /review (general code review), /code-review / /simplify (the code-cleaning family), and /red-team (security) — same spawn-and-collate shape, different lens. Reach for /a11y when you specifically want accessibility, with the option to comment or fix.
  • CI runs the same engines automatically (#730, mirroring the red-team workflows): .github/workflows/a11y.yml runs the subagent at depth=quick on a PR diff — posts a sticky <!-- a11y --> comment and fails the check on a 🔴 critical/serious finding (diff-scoped, so never on the pre-existing backlog). .github/workflows/a11y-daily.yml runs /a11y at depth=deep daily — writes a report (writeups/reports/a11y-repo-*.md), updates a public standing issue, and files a11y issues for new confirmed criticals. /a11y is the on-demand brain and human entry point for the same machinery. Both workflows MUST follow the Claude-action workflow standard in .claude/agents/CLAUDE.md — notably the tool-permission grant (per-PR: --allowedTools …; daily sweep: --permission-mode bypassPermissions), without which the gate fails open (#834/#1036).