Load this skill when configuring axe-core scans, reviewing automated accessibility test results, or writing tests that use @axe-core/playwright, @axe-core/react, or similar integrations. Provides a quick reference to axe 4.x rule IDs, their WCAG mapping, and default severity levels.
Installs into .claude/skills of the current project.
Are you the author of Source Cfc6c90c86b02743956771de?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/gutsofpratik-source-cfc6c90c86b02743956771de)
---
name: axe-rules
description: >
Load this skill when configuring axe-core scans, reviewing automated
accessibility test results, or writing tests that use @axe-core/playwright,
@axe-core/react, or similar integrations. Provides a quick reference to
axe 4.x rule IDs, their WCAG mapping, and default severity levels.
---
# Axe-Core Rules Reference Skill
> **Canonical source**: `examples/AXE_RULES_REFERENCE.md` and `examples/AXE_RULES_COVERAGE.md` in `mgifford/ACCESSIBILITY.md`
> This skill is derived from those files. When in doubt, the example is authoritative.
> **Note:** the canonical files are organized around a specific test-fixture scanner
> project (numbered test pages). This skill re-presents the same rule set and
> severities as a general-purpose reference, independent of any particular fixture.
Apply these rules when writing, reviewing, or debugging axe-core scans.
---
## Core Principle
Axe-core catches ~30–40% of WCAG issues automatically. It is the baseline,
not the ceiling. Always pair automated scanning with manual keyboard and screen
reader testing.
**An axe-core result is evidence, not a conformance decision.** Preserve the
rule's raw outcome (`violations`, `incomplete`, `passes`, `inapplicable`)
exactly as axe reports it — do not overwrite or discard it when converting
to a project finding. Cite this file, `AXE_RULES_REFERENCE.md`, or the
rule's own `dequeuniversity.com/rules/axe/` page as the authority for a
WCAG mapping; do not invent a mapping axe-core does not document for that
rule. Record an unreviewed `violations` result as
`evidence_status: automated-indicator` with `handling: review` — never as a
confirmed standards failure — until a human confirms it. See
[Accessibility Finding Tracking: Policy Classification](https://mgifford.github.io/ACCESSIBILITY.md/examples/ACCESSIBILITY_FINDING_TRACKING.html#policy-classification).
**Key tags to include in scans:**
```typescript
.withTags(['wcag2a', 'wcag2aa', 'wcag21aa', 'wcag22aa'])
```
Axe-core 4.11 includes 100+ automated checks across these categories: WCAG 2.0
A/AA (68 rules), WCAG 2.1 A/AA (26 rules), WCAG 2.2 A/AA (1 rule, disabled by
default), Best Practices (30+ rules), WCAG AAA (3 rules, disabled by default),
Experimental (9 rules, disabled by default), and Deprecated (7 rules, disabled
by default).
---
## Severity Scale (axe-core)
| axe severity | Meaning |
|---|---|
| **critical** | Blocks access for one or more disability groups |
| **serious** | Significantly impairs access |
| **moderate** | Creates friction; workaround exists |
| **minor** | Best-practice gap; marginal impact |
---
## Critical Rules — must never appear in production
| Rule ID | Category | Description |
|---|---|---|
| `image-alt` | Text alternatives | `<img>` missing `alt` attribute |
| `area-alt` | Text alternatives | Image map `<area>` missing alt text |
| `input-image-alt` | Text alternatives | `<input type="image">` missing alt |
| `video-caption` | Text alternatives | `<video>` without captions |
| `aria-allowed-attr` | ARIA | Element has unsupported ARIA attributes for its role |
| `aria-hidden-body` | ARIA | `aria-hidden` applied to `<body>` |
| `aria-required-attr` | ARIA | Required ARIA attribute missing |
| `aria-required-children` | ARIA | Required child ARIA roles missing |
| `aria-required-parent` | ARIA | Required parent ARIA role missing |
| `aria-roles` | ARIA | Invalid ARIA role value |
| `aria-valid-attr` | ARIA | Invalid ARIA attribute name |
| `aria-valid-attr-value` | ARIA | ARIA attribute has invalid value |
| `label` | Forms | Form input without associated `<label>` |
| `select-name` | Forms | `<select>` without accessible name |
| `button-name` | Buttons/Links | Button without discernible text |
| `input-button-name` | Buttons/Links | `<input type="button">` without text |
| `duplicate-id-aria` | Structure | Duplicate `id` values referenced by ARIA |
| `meta-refresh` | Meta | `<meta http-equiv="refresh">` causes timed redirect |
---
## Serious Rules — fix before release
| Rule ID | Category | Description |
|---|---|---|
| `object-alt` | Text alternatives | `<object>` without accessible text |
| `svg-img-alt` | Text alternatives | SVG with `role="img"` and no accessible name |
| `role-img-alt` | Text alternatives | Element with `role="img"` lacks alt text |
| `aria-braille-equivalent` | ARIA | Braille label without non-braille equivalent |
| `aria-command-name` | ARIA | Button, link, or menu item without accessible name |
| `aria-conditional-attr` | ARIA | ARIA attribute used contrary to role spec |
| `aria-hidden-focus` | ARIA | `aria-hidden` element contains focusable content |
| `aria-input-field-name` | ARIA | ARIA input field without accessible name |
| `aria-meter-name` | ARIA | ARIA `meter` element without accessible name |
| `aria-progressbar-name` | ARIA | ARIA `progressbar` element without accessible name |
| `aria-prohibited-attr` | ARIA | Prohibited ARIA attribute used |
| `aria-toggle-field-name` | ARIA | ARIA toggle field without accessible name |
| `aria-tooltip-name` | ARIA | ARIA tooltip without accessible name |
| `aria-dialog-name` | ARIA | Dialog without accessible name |
| `aria-treeitem-name` | ARIA | Tree item without accessible name |
| `color-contrast` | Color | Text contrast below 4.5:1 (normal) or 3:1 (large) |
| `frame-title` | Frames | `<iframe>` without accessible name |
| `frame-title-unique` | Frames | Frame titles are not unique |
| `frame-focusable-content` | Frames | Frame with focusable content is `tabindex="-1"` |
| `html-has-lang` | Language | `<html>` missing `lang` attribute |
| `html-lang-valid` | Language | `lang` attribute has invalid BCP 47 value |
| `valid-lang` | Language | Inline `lang` value is invalid |
| `document-title` | Language | `<title>` element missing |
| `list` | Structure | `<ul>`/`<ol>` contains elements other than `<li>` |
| `listitem` | Structure | `<li>` outside `<ul>` or `<ol>` |
| `definition-list` | Structure | `<dl>` contains elements other than `<dt>`/`<dd>` |
| `dlitem` | Structure | `<dt>`/`<dd>` outside `<dl>` |
| `bypass` | Keyboard | No skip navigation mechanism present |
| `scrollable-region-focusable` | Keyboard | Scrollable region not keyboard accessible |
| `link-name` | Buttons/Links | Link without discernible text |
| `link-in-text-block` | Buttons/Links | Link not distinguishable from surrounding text |
| `avoid-inline-spacing` | Content sizing | Text spacing overrides not adjustable |
| `autocomplete-valid` | Forms | `autocomplete` attribute invalid or missing |
| `tabindex` | Best practices | `tabindex` value > 0 used |
| `label-title-only` | Best practices | Form field labeled only via `title` attribute |
| `label-content-name-mismatch` | Best practices (experimental) | Visible label text doesn't match accessible name |
| `td-has-header` | Tables (experimental) | Large table cells lack associated headers |
| `table-fake-caption` | Tables (experimental) | Table caption not marked up with `<caption>` |
| `p-as-heading` | Best practices (experimental) | Paragraph styled to look like a heading |
---
## Moderate Rules — fix in near-term backlog
| Rule ID | Category | Description |
|---|---|---|
| `heading-order` | Headings | Heading levels skipped (e.g., h1 → h3) |
| `empty-heading` | Headings | Heading has no discernible text |
| `th-has-data-cells` | Tables | Table headers have no associated data cells |
| `td-headers-attr` | Tables | Table cells reference invalid header IDs |
| `meta-viewport` | Meta | Viewport `user-scalable=no` disables zoom |
| `html-xml-lang-mismatch` | Language | `lang` and `xml:lang` attributes disagree |
| `form-field-multiple-labels` | Forms | Form field has multiple conflicting labels |
| `landmark-one-main` | Best practices | Page has no `<main>` landmark |
| `landmark-banner-is-top-level` | Best practices | `banner` landmark not at top level |
| `landmark-contentinfo-is-top-level` | Best practices | `contentinfo` landmark not at top level |
| `landmark-main-is-top-level` | Best practices | `main` landmark not at top level |
| `landmark-no-duplicate-banner` | Best practices | More than one `banner` landmark |
| `landmark-no-duplicate-contentinfo` | Best practices | More than one `contentinfo` landmark |
| `landmark-no-duplicate-main` | Best practices | More than one `main` landmark |
| `region` | Best practices | Content exists outside landmark regions |
| `page-has-heading-one` | Best practices | Page has no `<h1>` |
| `scope-attr-valid` | Best practices | `scope` attribute has invalid value |
| `accesskeys` | Best practices | `accesskey` values are not unique |
---
## Minor Rules
| Rule ID | Category | Description |
|---|---|---|
| `table-duplicate-name` | Tables | `<caption>` text duplicates `summary` attribute |
| `aria-deprecated-role` | ARIA | Deprecated ARIA role used |
| `server-side-image-map` | Keyboard | Server-side image map used |
| `aria-text` | ARIA (best practice) | `role="text"` on an element with focusable descendants |
Disabled-by-default rules (deprecated `marquee`/`blink`/`audio-caption`) are not
listed above — flag them if encountered, but they are not enabled in a default scan.
The three WCAG AAA rules axe-core disables by default report against a
level the project's AA target does not require. If enabled and a project
has not declared a broader target, classify a confirmed finding from one of
these rules as `obligation: aspirational`, not `advisory` — it is still a
real AAA-level finding, just not part of the AA baseline. Do not report
satisfying one of these rules as WCAG AAA conformance.
---
## Running axe-core in CI (Playwright)
```typescript
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';
test('No WCAG 2.x AA violations', async ({ page }) => {
await page.goto('/');
const results = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa', 'wcag21aa', 'wcag22aa'])
.analyze();
expect(results.violations).toEqual([]);
});
```
**Common configuration options:**
```typescript
// Exclude a specific element (use sparingly — document the reason)
.exclude('#known-false-positive')
// Include only a specific element
.include('#component-under-test')
// Disable a specific rule (document the reason and JIRA/issue link)
.disableRules(['color-contrast'])
```
---
## Definition of Done Checklist
* [ ] axe-core scans run on every PR with `wcag2a`, `wcag2aa`, `wcag21aa`, `wcag22aa` tags
* [ ] Zero critical violations in production code
* [ ] Zero serious violations in production code (or explicit waiver with issue link)
* [ ] All rule exclusions documented with reason and associated issue
* [ ] Light and dark colour schemes both tested
* [ ] Mobile viewport tested
* [ ] axe results published as CI artifact for reference
---
## Key WCAG Criteria (automation coverage)
* 1.1.1 Non-text Content (A)
* 1.3.1 Info and Relationships (A)
* 1.4.3 Contrast Minimum (AA)
* 2.4.2 Page Titled (A)
* 3.1.1 Language of Page (A)
* 4.1.2 Name, Role, Value (A)
* 4.1.3 Status Messages (AA) — partial coverage
> Automation covers ~30–40% of WCAG issues. Always pair with manual testing.
---
## References
* [Full axe rules reference](https://github.com/mgifford/ACCESSIBILITY.md/blob/main/examples/AXE_RULES_REFERENCE.md)
* [Axe rules coverage summary](https://github.com/mgifford/ACCESSIBILITY.md/blob/main/examples/AXE_RULES_COVERAGE.md)
* [axe-core rule descriptions](https://dequeuniversity.com/rules/axe/)
* [@axe-core/playwright](https://github.com/dequelabs/axe-core-npm/tree/develop/packages/playwright)
* [axe-core GitHub](https://github.com/dequelabs/axe-core)
> **Standards horizon:** These rules target WCAG 2.2 AA.
> Monitor: <https://www.w3.org/TR/wcag-3.0/>