Axe-Core Rules Reference Skill
Canonical source:
examples/AXE_RULES_REFERENCE.mdandexamples/AXE_RULES_COVERAGE.mdinmgifford/ACCESSIBILITY.mdThis 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.
Key tags to include in scans:
.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)
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:
// 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,wcag22aatags - 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
- Axe rules coverage summary
- axe-core rule descriptions
- @axe-core/playwright
- axe-core GitHub
Standards horizon: These rules target WCAG 2.2 AA. Monitor: https://www.w3.org/TR/wcag-3.0/