Testland
Browse all skills & agents

wcag-compliance-reporter

Builds a per-page WCAG 2.2 compliance score report by aggregating output from one or more accessibility scanners (axe-core / pa11y / lighthouse / WAVE / IBM Equal Access), pivoting violations by Success Criterion (1.4.3 contrast, 2.4.7 focus visible, etc.), grouping by conformance level (A / AA / AAA), reporting per-page coverage gaps explicitly (the "this page wasn't scanned" failure mode), and emitting both an executive summary and a per-page drill-down. Use after a multi-page accessibility scan - pa11y-ci, axe across a sitemap, lighthouse-batch - when the team needs a shareable conformance report rather than a per-page tool dump.

Install with skills.sh (any agent)

npx skills add testland/qa --skill wcag-compliance-reporter
View source

wcag-compliance-reporter

Overview

WCAG 2.2 has 77 success criteria (SC) split across three conformance levels: 25 at A, 24 at AA, 28 at AAA (wcag-spec (opens in new window)) - for a total of 49 SC at the AA level (which is the typical legal / contractual target in the US, EU, UK).

Per wcag-conformance (opens in new window):

"Level AA: 'satisfies all the Level A and Level AA success criteria'"

"All success criteria at a claimed level must be satisfied with no exceptions."

That binary verdict ("conforms or doesn't") makes per-tool output ("axe found 17 violations in 5 pages") hard to act on - the team needs to know which SC at which level on which page is failing the conformance claim.

This skill builds that report.

When to use

  • A multi-page scan has produced output from one or more of: axe-core (via @axe-core/cli or axe-puppeteer), pa11y (via pa11y-ci), Lighthouse (via lighthouse-batch), WAVE, IBM Equal Access.
  • A stakeholder asks "are we WCAG 2.2 AA conformant?" and wants a document, not a CLI dump.
  • The team is preparing an accessibility audit / VPAT / ACR.
  • A PR introduces UI changes and the team needs a "before vs after" on conformance.

If only one tool is in use, the tool's native HTML report may suffice; this skill is for aggregation across multiple tools or multiple pages.

How to use

  1. Gather scan output from one or more scanners (axe-core, pa11y, Lighthouse, WAVE, IBM Equal Access) across every page in scope.
  2. Normalize per tool into one SC-keyed Violation shape, mapping each tool's rule ID to the WCAG Success Criterion it covers (Step 1).
  3. Aggregate the normalized violations by SC and by page (Step 2).
  4. Compute the conformance verdict per level (A / AA / AAA): conformant, non-conformant, or unknown - where unknown means the scan never covered the SCs a claim at that level needs (Step 3).
  5. Check page coverage against the pages-to-scan.yaml process spec and flag any page that wasn't scanned (Step 4).
  6. Render the report - the executive verdict plus per-SC and per-page markdown (Step 5), then emit the machine-readable compliance.json and the CI job that produces it (Machine output and CI, below).

Step 1 - Author normalizers per upstream tool

Each scanner has its own JSON shape. The first step is normalizing to a single intermediate format keyed by SC:

interface Violation {
  page: string;                       // URL or path
  successCriterion: string;           // e.g. "1.4.3"
  level: 'A' | 'AA' | 'AAA';
  ruleId: string;                     // tool-specific rule (e.g. axe "color-contrast")
  selector: string;                   // CSS selector of failing element
  message: string;
  helpUrl?: string;                   // tool's docs
  scanner: 'axe' | 'pa11y' | 'lighthouse' | 'wave' | 'equal-access';
}

Normalizer per tool maps tool-specific rule IDs to the SC they cover, and a central sc-mapping.json holds rule-to-SC for every tool the report consumes. The per-tool tag conventions (axe tags, pa11y codes, Lighthouse's internal map), a worked normalize_axe example, and the sc-mapping.json shape live in references/tool-normalizers.md.

Step 2 - Aggregate

def aggregate(violations):
    by_sc = defaultdict(lambda: defaultdict(list))   # sc -> level -> [violation]
    by_page = defaultdict(list)                       # page -> [violation]
    pages = set()
    for v in violations:
        by_sc[v['successCriterion']][v['level']].append(v)
        by_page[v['page']].append(v)
        pages.add(v['page'])
    return {
        'by_sc': dict(by_sc),
        'by_page': dict(by_page),
        'pages': sorted(pages),
    }

Step 3 - Conformance verdict per level

Per wcag-conformance (opens in new window), conformance is binary per level ("All success criteria at a claimed level must be satisfied with no exceptions"). The verdict logic:

def conformance(agg, level):
    """Returns 'conformant' | 'non-conformant' | 'unknown'.
    Unknown if the scan didn't cover the SCs needed to claim conformance."""
    failing_scs = [sc for sc, by_level in agg['by_sc'].items()
                       if any(v['level'] in level_set(level) for v in
                              [vv for lvl, vs in by_level.items() for vv in vs])]
    if failing_scs:
        return 'non-conformant', failing_scs
    # Did the scan actually cover all SCs needed for this level?
    expected_scs = SC_BY_LEVEL[level]   # ground-truth list per WCAG 2.2
    covered_scs  = set(agg['by_sc'].keys())  # only SCs at least one tool checks
    missing = expected_scs - covered_scs
    if missing:
        return 'unknown', sorted(missing)
    return 'conformant', []

level_set('AA') returns {'A', 'AA'} because AA "satisfies all the Level A and Level AA success criteria" (wcag-conformance (opens in new window)).

The "unknown" verdict is critical - automated tools cover roughly 30% of WCAG SCs. A green automated scan ≠ AA conformance. The report must surface what wasn't scanned.

Step 4 - Per-page coverage gaps

Equally important: did every relevant page get scanned at all? A checkout flow with a missing scan on the payment page can't claim conformance per wcag-conformance (opens in new window):

"Complete Processes: When a page is part of a multi-step process, 'all web pages in the process conform at the specified level or better.'"

Author maintains a pages-to-scan.yaml:

processes:
  checkout:
    pages:
      - /
      - /products
      - /cart
      - /checkout
      - /checkout/payment
      - /checkout/confirm
  account:
    pages:
      - /account/login
      - /account/profile
      - /account/orders

Compare scanned pages against the spec; flag missing ones in the report.

Step 5 - Render the report

Render the executive verdict first: a three-row table mapping each conformance level to its verdict and the reason.

Conformance levelVerdictReason
A❌ non-conformant3 SC failing on 7 pages
AA❌ non-conformant+ 5 additional SC failing on 12 pages
AAA⚠ unknown14 of 28 AAA SC weren't checked by any scanner

Below the verdict, the full report adds process-coverage, failures-by-SC, per-page-detail, and coverage-gap sections - the coverage-gap table is where the "automated tools cover ~30% of SCs, the rest is manual review" reality from Step 3 lands. The complete multi-table sample report, every section with example rows, lives in references/sample-report-format.md.

Machine output and CI

Once the markdown report renders, two operational blocks finish the pipeline: a machine-readable compliance.json for downstream dashboards / ACR gates, and the CI job that runs the scanners and aggregator on every push. Both templates live in references/machine-output-and-ci.md.

Anti-patterns

Anti-patternWhy it failsFix
Reporting "0 axe violations" as "WCAG conformant"Automated tools cover ~30% of SCs; the rest needs manual review.Always include the "manual review required" section (Step 5).
Treating all violation severities equallyA contrast issue on a button isn't equivalent to a missing alt on a decoration.Group by SC level (A vs AA vs AAA); within a level, count instances.
Aggregating across pages without page coverage specMissing pages don't show up; the team thinks they're covered.Author pages-to-scan.yaml; report missing pages explicitly (Step 4).
Declaring conformance from a single-tool scanEach tool has different SC coverage; a clean axe run can hide pa11y-detectable issues.Use 2+ tools; deduplicate by (page, SC, selector).
Per-tool report dumps with no SC mappingReviewer can't tell if tool-rule "color-contrast" maps to SC 1.4.3.Maintain sc-mapping.json (Step 1).
Reporting AAA verdict as a fail without contextAAA is rarely the target; failing AAA isn't a failure for an AA-targeting team.Make the target level explicit; report the other levels as "informational".
Ignoring "unknown" verdict (treating uncovered SCs as covered)False conformance claim; potential legal exposure.Surface the unknown verdict in bold (Step 3); list the unscanned SCs.

Limitations

  • Automated scans don't replace manual review. Roughly 30% of WCAG SCs are tool-detectable; the rest require human assessment (cognitive load, alternative text quality, color-only meaning, etc.).
  • No screen-reader reality check. Tools find the absence of ARIA; they don't verify that the screen-reader narration is helpful. Pair with screen-reader-test-author from qa-accessibility.
  • Per-tool drift. Rule catalogs evolve; new tool versions may add or remove SC coverage. Pin tool versions in CI; bump the SC mapping in lock-step.
  • VPAT / ACR is a different document. This report informs the VPAT but isn't the VPAT itself - those documents have specific formats (Section 508, EN 301 549) that go beyond WCAG.

References

  • wcag-tr (opens in new window) - WCAG 2.2 specification: 77 SC across A / AA / AAA, four POUR principles, 13 guidelines.
  • wcag-conformance (opens in new window) - WCAG 2.2 conformance requirements: binary per-level verdict, complete-processes rule, no-exceptions rule.
  • junit-xml-analysis (in the qa-test-reporting plugin) - reporter for test execution (different domain, same PR-time reporting shape).
  • The qa-accessibility plugin's per-tool wrappers (axe-a11y, pa11y-a11y, lighthouse-a11y, wave-a11y, ibm-equal-access-a11y) - produce the upstream input this skill consumes.
  • Per-tool normalizers + the sc-mapping.json shape (Step 1 deep dive): references/tool-normalizers.md.
  • The full multi-table sample report format (Step 5 deep dive): references/sample-report-format.md.
  • Machine-readable compliance.json output + the CI aggregation job: references/machine-output-and-ci.md.

Machine-readable output and CI integration

View source (opens in new window)

Machine-readable output and CI integration

Deep reference for wcag-compliance-reporter SKILL.md. Consult when wiring the compliance report into a dashboard / ACR pipeline or running the aggregation in CI on every push.

Machine-readable output

In addition to the markdown, emit compliance.json for downstream consumption (dashboards, ASR, programmatic gates):

{
  "generatedAt": "2026-05-05T14:00:00Z",
  "site": "example.com",
  "verdict": { "A": "non-conformant", "AA": "non-conformant", "AAA": "unknown" },
  "processes": [
    { "name": "checkout", "pagesSpec": [...], "pagesScanned": [...], "complete": false },
    { "name": "account", "pagesSpec": [...], "pagesScanned": [...], "complete": true }
  ],
  "violations": [...]
}

CI integration

- name: Run scanners
  run: |
    npx pa11y-ci   --json > pa11y.json
    npx @axe-core/cli https://staging.example.com > axe.json
    npx lighthouse-batch -s https://staging.example.com -o reports/

- name: Aggregate + report
  run: python scripts/wcag_compliance.py \
        --pa11y pa11y.json \
        --axe axe.json \
        --lighthouse reports/ \
        --pages-spec pages-to-scan.yaml \
        --out compliance/

- name: Upload
  uses: actions/upload-artifact@v4
  with:
    name: wcag-compliance
    path: compliance/

WCAG 2.2 compliance report - full sample format

View source (opens in new window)

WCAG 2.2 compliance report - full sample format

Deep reference for wcag-compliance-reporter SKILL.md. Consult when rendering the whole multi-table report; SKILL.md Step 5 keeps only the top verdict table and points here for the rest.

The complete report renders the executive verdict first, then process-coverage, failures-by-SC, per-page-detail, and coverage-gap sections. The full template (every section with example rows):

# WCAG 2.2 Compliance Report

**Generated:** 2026-05-05
**Site:** example.com
**Scope:** 22 pages across 4 processes
**Tools used:** axe-core 4.10.3, pa11y 8.1.0, Lighthouse 12.5.0

## Verdict

| Conformance level | Verdict | Reason |
|-------------------|---------|--------|
| A   | ❌ non-conformant | 3 SC failing on 7 pages |
| AA  | ❌ non-conformant | + 5 additional SC failing on 12 pages |
| AAA | ⚠ unknown | 14 of 28 AAA SC weren't checked by any scanner |

> Per [wcag-conformance][wcag-conf]: "All success criteria at a
> claimed level must be satisfied with no exceptions." Failing
> means the conformance claim cannot be made for this level.

## Process coverage

| Process   | Pages spec | Pages scanned | Missing                         |
|-----------|-----------:|--------------:|---------------------------------|
| checkout  | 6          | 5             | `/checkout/confirm` not scanned |
| account   | 3          | 3             | -                              |

⚠ Until `/checkout/confirm` is scanned, the **checkout process**
cannot claim conformance regardless of per-page results
([wcag-conformance][wcag-conf] "Complete Processes").

## Failures by Success Criterion (Level A + AA)

| SC      | Title                              | Level | Pages affected | Total instances |
|---------|------------------------------------|-------|---------------:|----------------:|
| 1.4.3   | Contrast (Minimum)                 | AA    |             7  |              23 |
| 2.4.7   | Focus Visible                      | AA    |             4  |               8 |
| 3.3.2   | Labels or Instructions             | A     |             3  |               5 |
| 4.1.2   | Name, Role, Value                  | A     |             6  |              14 |
| 1.1.1   | Non-text Content                   | A     |             2  |               4 |

(Click an SC for per-page detail.)

## Per-page detail (top failing pages)

### `/checkout/payment` - 12 violations across 5 SC

| SC    | Tool    | Selector                          | Message |
|-------|---------|-----------------------------------|---------|
| 1.4.3 | axe     | `.amount-due`                      | Foreground/background contrast 3.2:1 (need 4.5:1 for normal text) |
| 2.4.7 | axe     | `button.continue-payment`           | Element does not have a visible focus indicator |
| ...

(Full per-page tables follow.)

## Coverage gaps - automated tools don't cover everything

| SC level | Total SCs | Covered by ≥1 tool | Manual review required |
|----------|----------:|-------------------:|------------------------|
| A        |    25     |        15          |  10 SCs                |
| AA       |    24     |        17          |   7 SCs                |
| AAA      |    28     |        14          |  14 SCs                |

**Manual review SC list (excerpt):** 1.2.1 (Audio-only / Video-only),
2.2.2 (Pause / Stop / Hide), 3.1.5 (Reading Level), ... See
`docs/manual-checklist.md` for the per-SC checklist.

The verdict rows restate the binary per-level rule from wcag-conformance (opens in new window) ("All success criteria at a claimed level must be satisfied with no exceptions"): a failing level cannot claim conformance, and an unknown level means the scan never covered the SCs that level's claim needs.

Per-tool normalizers and the SC mapping

View source (opens in new window)

Per-tool normalizers and the SC mapping

Deep reference for wcag-compliance-reporter SKILL.md. Consult when writing or updating a per-tool normalizer; SKILL.md Step 1 keeps only the Violation contract and the concept and points here for the implementation.

Each scanner tags violations with its own WCAG hints, so each tool needs a small normalizer that maps its native rule IDs to a WCAG Success Criterion. The mapping is curated upstream:

  • axe-core ships tags like wcag2a, wcag143.
  • pa11y ships codes like WCAG2AA.Principle1.Guideline1_4.1_4_3.
  • Lighthouse uses an internal mapping documented in its audit catalog.

Worked normalizer - axe-core

# scripts/normalize_axe.py
def normalize_axe(json_blob, page_url):
    out = []
    for violation in json_blob.get('violations', []):
        sc = sc_from_axe_tags(violation['tags'])  # e.g. "1.4.3"
        if not sc: continue
        for node in violation['nodes']:
            out.append({
                'page': page_url,
                'successCriterion': sc,
                'level': level_from_sc(sc),       # "1.4.3" → "AA"
                'ruleId': violation['id'],
                'selector': ' '.join(node['target']),
                'message': violation['help'],
                'helpUrl': violation['helpUrl'],
                'scanner': 'axe',
            })
    return out

The central SC mapping

A single sc-mapping.json file holds rule-to-SC for every tool the report consumes. Update it whenever a tool's rule catalog changes so a renamed or newly-added rule keeps resolving to the right Success Criterion; a stale mapping silently drops violations that no longer match a known rule ID.

Related skills

a11y-violation-gate

Builds a CI gate that fails the build on **new** WCAG / a11y violations introduced by a PR while grandfathering pre-existing violations on a per-rule / per-page baseline. Aggregates verdicts from axe-core / pa11y / Lighthouse a11y / WAVE / IBM Equal Access scans. Use when a project has accumulated a11y debt and a strict "zero violations" gate would block every PR - the ratchet pattern lets the team ship while preventing regressions.

aria-authoring-patterns

Reference for the W3C ARIA Authoring Practices Guide (APG) - covers the 31 canonical interactive-widget patterns (Combobox, Dialog, Menu, Tabs, Tree, etc.), their required ARIA roles and states, the keyboard-interaction model per pattern, and the canonical-violations to watch for. Use when authoring a custom interactive widget that doesn't have a native HTML equivalent, or when reviewing one for ARIA correctness.

axe-a11y

Authors and runs axe-core accessibility scans - the most-deployed open-source a11y engine - via the `axe.run()` JavaScript API or the @axe-core/playwright / @axe-core/cli wrappers, parses the `violations[]` results into per-rule severity (critical / serious / moderate / minor), configures rule disable / disable-by-tag patterns, and emits JUnit-shaped output for CI gating. Use when the project ships UI tests in JavaScript / TypeScript and wants automated a11y coverage on every PR.

ibm-equal-access-a11y

Authors and runs IBM Equal Access accessibility-checker scans - IBM's open-source a11y engine with WCAG 2.0 / 2.1 / 2.2 + US Section 508 rule sets, integrating with Node / Selenium / Puppeteer / Playwright / Karma / Cypress test runners. Distinguished by IBM's enterprise-tier rule coverage and Section 508 specificity. Use when the project ships to US federal / public-sector customers (Section 508 mandate) or when the team values IBM-branded a11y reporting.

lighthouse-a11y

Configures Lighthouse CI's Accessibility category for automated accessibility testing (a11y / WCAG coverage) - `categories:accessibility` audits backed by axe-core (axe) - with per-URL minimum-score assertions (fail a build when a page's score drops below a threshold) and per-audit overrides, distinct from the Performance category that `lighthouse-perf` covers. Use when the project already runs Lighthouse CI for Web Vitals and the team wants to add accessibility coverage in the same pipeline rather than spinning up a separate scanner.

pa11y-a11y

Authors and runs pa11y accessibility scans - a CLI / Node.js tool that wraps HTML CodeSniffer (htmlcs) and / or axe-core engines - with `pa11y {url}` invocation, reporter selection (cli / csv / json / html / tsv), WCAG standard selection (WCAG2A / WCAG2AA / WCAG2AAA), and rule ignoring. Use when the project needs scriptable a11y scans without a full test framework, or when a Node-stack project wants an alternative to direct axe-core use.

screen-reader-test-author

Builds a screen-reader test narrative - a step-by-step manual test script for NVDA (Windows), JAWS (Windows), VoiceOver (macOS / iOS), or TalkBack (Android) - that exercises a specific user flow through a component or page and captures the expected announcement at each step. Use when authoring an accessibility-acceptance test the team will run before sign-off, OR when scripting a manual a11y audit.

wave-a11y

Runs WebAIM WAVE accessibility scans via the WAVE API or the browser-extension UI - produces visual overlay of errors / alerts / structural elements directly on the page, plus categorized JSON output for CI use. Use when the team values manual-review-friendly visual feedback (the WAVE overlay) alongside automated CI scans, or when a regulatory audit requires WebAIM-branded reports.

wcag-checklist-builder

Builds a per-component WCAG 2.2 accessibility checklist from a component spec - covers focus management, color contrast, ARIA roles & states, keyboard interaction, error handling, and live-region announcements - emitting a markdown checklist or YAML test plan that pairs with screen-reader-test-author for manual verification and the violation gate for automated scans. Use during component-spec review or pre-implementation acceptance.

wcag-color-contrast

Reference for WCAG 2.2 color-contrast conformance - covers SC 1.4.3 Contrast (Minimum, AA), 1.4.6 Contrast (Enhanced, AAA), 1.4.11 Non-text Contrast (AA), and 1.4.13 Content on Hover or Focus (AA) - with the canonical contrast ratios (4.5:1 normal text, 3:1 large text and UI components), measurement formula references, and bulk design-token checking patterns. Use when designing a color palette, reviewing a component for accessibility, or auditing existing CSS for contrast violations.

wcag-focus-trap

Reference for **intentional** focus management in modal / dialog / drawer / popover components - the canonical pattern that satisfies WCAG SC 2.4.3 (Focus Order) without violating SC 2.1.2 (No Keyboard Trap). Covers focus-on-open, focus-cycle-within-container, Escape-closes-and-restores, return-to-trigger, and inert-the-rest-of-the-page. Use when authoring or reviewing any component that displays content over the page (modals, drawers, popovers, command palettes).

wcag-keyboard-navigation

Reference catalog for WCAG 2.2 keyboard-navigation conformance - covers SC 2.1.1 (Keyboard), 2.1.2 (No Keyboard Trap), 2.1.4 (Character Key Shortcuts), 2.4.3 (Focus Order), 2.4.7 (Focus Visible), 2.4.11/2.4.12 (Focus Not Obscured) - with conformance levels (A/AA), test scripts, and per-criterion failure patterns. Use when authoring or reviewing keyboard-only interaction support.

widget-a11y-test-matrix

Per-widget manual accessibility test matrices where every row pairs one keystroke with the expected focus behavior, the expected NVDA announcement, the expected VoiceOver announcement, and the WCAG 2.2 success criterion that row verifies. Covers button, toggle button, checkbox, text input, modal dialog, menu button, and combobox archetypes, plus universal Tab traversal. Use when a rendered widget has cleared automated scanning and a tester needs a fill-in pass/fail sheet to run by hand against NVDA and VoiceOver.