A TypeScript accessibility scanner aimed at being W3C ACT (Accessibility Conformance Testing) rules compatible. Rules from axe-core that aren't yet codified in ACT are implemented in a separate section.
Implementation status against the ACT Rules Community Group catalogue. Each rule links to its ACT specification. The Test cases column shows how many of the ACT-supplied test cases this scanner runs and passes — out of the total applicable cases for that rule. Test cases that depend on visual rendering, media playback, keyboard interaction, or other capabilities outside the scanner's scope are intentionally not generated.
The W3C ACT implementations report only counts a rule as "consistently implemented" when the implementation passes every published ACT test case for it. By that measure:
- 20 ACT rules pass every generated test case (W3C "consistent implementation" criterion).
- 14 more rules pass at least one test case but not all.
- 53 / 88 ACT rules have some scanner implementation, even if untested.
- 296 / 843 ACT test cases (35%) are exercised against the scanner.
| Implemented | ACT Rule | WCAG | axe-core rule(s) | Test cases |
|---|---|---|---|---|
| ✅ | 73f2c2 — autocomplete attribute has valid value |
1.3.5 | autocomplete-valid |
0 / 19 |
| ✅ | 5f99a7 — ARIA attribute is defined in WAI-ARIA | 1.3.1, 4.1.2 | aria-valid-attr |
7 / 7 |
| ❌ | kb1m8s — ARIA global properties not used where prohibited | 1.3.1 | — | 0 / 8 |
| ✅ | ff89c9 — ARIA required context role | 1.3.1 | aria-required-parent |
0 / 10 |
| ✅ | bc4a75 — ARIA required owned elements | 1.3.1 | aria-required-children |
0 / 20 |
| ✅ | 6a7281 — ARIA state or property has valid value | 1.3.1, 4.1.2 | aria-valid-attr-value |
0 / 17 |
| ✅ | 5c01ea — ARIA state or property is permitted | 1.3.1, 4.1.2 | aria-allowed-attr |
0 / 15 |
| ❌ | 1a02b0 — Audio and visuals of video element have transcript | 1.2.1, 1.2.8, 1.3.1 | — | 0 / 8 |
| ❌ | e7aa44 — Audio element content has text alternative | 1.2.1 | — | 0 / 5 |
| ✅ | 2eb176 — Audio element content has transcript | — | audio-caption |
0 / 9 |
| ✅ | afb423 — Audio element content is media alternative for text | — | audio-caption |
0 / 5 |
| ✅ | 80f0bf — Audio or video element avoids automatically playing audio | 1.4.2 | no-autoplay-audio |
4 / 5 |
| ❌ | 4c31df — Audio or video element that plays automatically has a control mechanism | — | — | 0 / 8 |
| ❌ | aaa1bf — Audio or video element that plays automatically has no audio that lasts more than 3 seconds | — | — | 0 / 4 |
| ✅ | 3e12e1 — Block of repeated content is collapsible | — | bypass |
3 / 7 |
| ✅ | 97a4e1 — Button has non-empty accessible name | 4.1.2 | aria-command-name, button-name, input-button-name |
12 / 12 |
| ✅ | cf77f2 — Bypass Blocks of Repeated Content | 2.4.1 | bypass |
9 / 13 |
| ❌ | 9bd38c — Content has alternative for visual reference | 1.3.3 | — | 0 / 19 |
| ❌ | 7677a9 — Device motion based changes to the content can also be created from the user interface | 2.5.4 | — | 0 / 5 |
| ❌ | c249d5 — Device motion based changes to the content can be disabled | 2.5.4 | — | 0 / 4 |
| ✅ | b40fd1 — Document has a landmark with non-repeated content | — | bypass |
0 / 7 |
| ✅ | ye5d6e — Document has an instrument to move focus to non-repeated content | — | bypass |
9 / 11 |
| ✅ | 047fe0 — Document has heading for non-repeated content | — | bypass |
10 / 13 |
| ❌ | oj04fd — Element in sequential focus order has visible focus | 2.4.7 | — | 0 / 6 |
| ✅ | 46ca7f — Element marked as decorative is not exposed | — | presentation-role-conflict |
9 / 9 |
| ✅ | 6cfa84 — Element with aria-hidden has no content in sequential focus navigation | 4.1.2 | aria-hidden-focus |
11 / 12 |
| ✅ | de46e4 — Element with lang attribute has valid language tag | 3.1.2 | valid-lang |
14 / 14 |
| ✅ | 307n5z — Element with presentational children has no focusable content | 4.1.2 | nested-interactive |
0 / 9 |
| ✅ | 4e8ab6 — Element with role attribute has required states and properties | 1.3.1, 4.1.2 | aria-required-attr |
0 / 11 |
| ✅ | 36b590 — Error message describes invalid form field value | 3.3.1 | error-message |
8 / 8 |
| ❌ | 80af7b — Focusable element has no keyboard trap | 2.1.2 | — | 0 / 12 |
| ❌ | ebe86a — Focusable element has no keyboard trap via non-standard navigation | — | — | 0 / 6 |
| ❌ | a1b64e — Focusable element has no keyboard trap via standard navigation | — | — | 0 / 7 |
| ✅ | e086e5 — Form field has non-empty accessible name | 1.3.1, 2.5.3, 4.1.2 | aria-input-field-name, aria-toggle-field-name, label, select-name |
19 / 19 |
| ❌ | cc0f0a — Form field label is descriptive | 2.4.6 | — | 0 / 13 |
| ✅ | a25f45 — Headers attribute specified on a cell refers to cells in the same table element | 1.3.1 | td-headers-attr |
12 / 12 |
| ✅ | ffd0e9 — Heading has non-empty accessible name | — | empty-heading |
13 / 13 |
| ❌ | b49b2e — Heading is descriptive | 2.4.6 | — | 0 / 10 |
| ❌ | off6ek — HTML element language subtag matches language | 3.1.2 | — | 0 / 9 |
| ❌ | 0va7u6 — HTML images contain no text | 1.4.5, 1.4.9 | — | 0 / 13 |
| ✅ | bf051a — HTML page lang attribute has valid language tag |
3.1.1 | html-lang-valid |
6 / 6 |
| ✅ | b5c3f8 — HTML page has lang attribute | 3.1.1 | html-has-lang |
5 / 5 |
| ✅ | 2779a5 — HTML page has non-empty title | 2.4.2 | document-title |
12 / 12 |
| ❌ | ucwvc8 — HTML page language subtag matches default language | 3.1.1 | — | 0 / 9 |
| ❌ | c4a8a4 — HTML page title is descriptive | 2.4.2 | — | 0 / 6 |
| ✅ | cae760 — Iframe element has non-empty accessible name | 4.1.2 | frame-title |
0 / 7 |
| ✅ | 4b1c6c — Iframe elements with identical accessible names have equivalent purpose | 4.1.2 | frame-title-unique |
0 / 14 |
| ✅ | akn7bn — Iframe with interactive elements is not excluded from tab-order | 2.1.1, 2.1.3 | frame-focusable-content |
3 / 3 |
| ❌ | qt1vmo — Image accessible name is descriptive | 1.1.1 | — | 0 / 6 |
| ✅ | 59796f — Image button has non-empty accessible name | 1.1.1, 4.1.2 | input-image-alt |
0 / 7 |
| ❌ | 9eb3f6 — Image filename is accessible name for image | 1.1.1 | — | — |
| ✅ | 23a2a8 — Image has non-empty accessible name | 1.1.1 | image-alt, role-img-alt |
13 / 13 |
| ✅ | e88epe — Image not in the accessibility tree is decorative | 1.1.1 | decorative-image |
6 / 10 |
| ✅ | 24afc2 — Important letter spacing in style attributes is wide enough | 1.4.12 | avoid-inline-spacing |
7 / 10 |
| ✅ | 78fd32 — Important line height in style attributes is wide enough | 1.4.12 | avoid-inline-spacing |
11 / 14 |
| ✅ | 9e45ec — Important word spacing in style attributes is wide enough | 1.4.12 | avoid-inline-spacing |
7 / 10 |
| ✅ | c487ae — Link has non-empty accessible name | 2.4.4, 2.4.9, 4.1.2 | area-alt, link-name |
12 / 22 |
| ❌ | 5effbb — Link in context is descriptive | 2.4.4, 2.4.9 | — | 0 / 15 |
| ❌ | aizyf1 — Link is descriptive | 2.4.9 | — | 0 / 9 |
| ❌ | fd3a94 — Links with identical accessible names and same context serve equivalent purpose | 2.4.4, 2.4.9 | — | 0 / 17 |
| ✅ | b20e66 — Links with identical accessible names have equivalent purpose | 2.4.9 | identical-links-same-purpose |
0 / 18 |
| ✅ | m6b1q3 — Menuitem has non-empty accessible name | 4.1.2 | button-name |
6 / 6 |
| ✅ | bc659a — Meta element has no refresh delay | 2.2.1, 2.2.4, 3.2.5 | meta-refresh |
7 / 7 |
| ✅ | bisz58 — Meta element has no refresh delay (no exception) | 2.2.1, 2.2.4, 3.2.5 | meta-refresh-no-exceptions |
5 / 5 |
| ✅ | b4f0c3 — Meta viewport allows for zoom | 1.4.10, 1.4.4 | meta-viewport |
12 / 12 |
| ❌ | ffbc54 — No keyboard shortcut uses only printable characters | 2.1.4 | — | 0 / 8 |
| ✅ | 8fc3b6 — Object element rendering non-text content has non-empty accessible name | 1.1.1 | object-alt |
10 / 10 |
| ✅ | b33eff — Orientation of the page is not restricted using CSS transforms | 1.3.4 | css-orientation-lock |
0 / 7 |
| ✅ | 674b10 — Role attribute has valid value | 1.3.1, 4.1.2 | aria-roles |
0 / 5 |
| ✅ | 0ssw9k — Scrollable content can be reached with sequential focus navigation | 2.1.1, 2.1.3 | scrollable-region-focusable |
4 / 6 |
| ✅ | 2t702h — Summary element has non-empty accessible name | 4.1.2 | summary-name |
8 / 8 |
| ✅ | 7d6734 — SVG element with explicit role has non-empty accessible name | 1.1.1 | svg-img-alt |
0 / 7 |
| ✅ | d0f69e — Table header cell has assigned cells | 1.3.1 | th-has-data-cells |
9 / 9 |
| ❌ | efbfc7 — Text content that changes automatically can be paused, stopped or hidden | 2.2.2 | — | 0 / 6 |
| ✅ | 09o5cg — Text has enhanced contrast | 1.4.3, 1.4.6 | color-contrast-enhanced |
0 / 24 |
| ✅ | afw4f7 — Text has minimum contrast | 1.4.3, 1.4.6 | color-contrast |
0 / 23 |
| ✅ | eac66b — Video element auditory content has accessible alternative | 1.2.2 | video-caption |
3 / 4 |
| ❌ | f51b46 — Video element auditory content has captions | 1.2.1 | — | 0 / 6 |
| ❌ | ab4d13 — Video element content is media alternative for text | — | — | 0 / 5 |
| ❌ | c5a4ea — Video element visual content has accessible alternative | 1.2.3, 1.2.5, 1.2.8 | — | 0 / 7 |
| ❌ | 1ea59c — Video element visual content has audio description | — | — | 0 / 5 |
| ❌ | 1ec09b — Video element visual content has strict accessible alternative | 1.2.5 | — | 0 / 5 |
| ❌ | c3232f — Video element visual-only content has accessible alternative | 1.2.1 | — | 0 / 7 |
| ❌ | d7ba54 — Video element visual-only content has audio track alternative | — | — | 0 / 3 |
| ❌ | ee13b5 — Video element visual-only content has transcript | 1.2.1, 1.3.1 | — | 0 / 6 |
| ❌ | fd26cf — Video element visual-only content is media alternative for text | — | — | 0 / 5 |
| ✅ | 2ee8b8 — Visible label is part of accessible name | 2.5.3 | label-content-name-mismatch |
10 / 11 |
| ❌ | 59br37 — Zoomed text node is not clipped with CSS overflow | 1.4.4 | — | 0 / 9 |
Rules implemented from the axe-core ruleset that don't have an equivalent in the ACT catalogue.
| Implemented | axe-core Rule | Description | Tags | Impact | Issue Type |
|---|---|---|---|---|---|
| ✅ | aria-hidden-body |
Ensures aria-hidden='true' is not present on the document body. | wcag2a, wcag412 | Critical | failure |
| ✅ | aria-meter-name |
Ensures every ARIA meter node has an accessible name | wcag2a, wcag111 | Serious | failure, needs review |
| ✅ | aria-progressbar-name |
Ensures every ARIA progressbar node has an accessible name | wcag2a, wcag111 | Serious | failure, needs review |
| ✅ | aria-roledescription |
Ensure aria-roledescription is only used on elements with an implicit or explicit role | wcag2a, wcag412 | Serious | failure, needs review |
| ✅ | aria-tooltip-name |
Ensures every ARIA tooltip node has an accessible name | wcag2a, wcag412 | Serious | failure, needs review |
| ✅ | blink |
Ensures <blink> elements are not used | wcag2a, wcag222, section508, section508.22.j | Serious | failure |
| ✅ | definition-list |
Ensures <dl> elements are structured correctly | wcag2a, wcag131 | Serious | failure |
| ✅ | dlitem |
Ensures <dt> and <dd> elements are contained by a <dl> | wcag2a, wcag131 | Serious | failure |
| ✅ | form-field-multiple-labels |
Ensures form field does not have multiple label elements | wcag2a, wcag332 | Moderate | needs review |
| ✅ | list |
Ensures that lists are structured correctly | wcag2a, wcag131 | Serious | failure |
| ✅ | listitem |
Ensures <li> elements are used semantically | wcag2a, wcag131 | Serious | failure |
| ✅ | marquee |
Ensures <marquee> elements are not used | wcag2a, wcag222 | Serious | failure |
| ✅ | server-side-image-map |
Ensures that server-side image maps are not used | wcag2a, wcag211, section508, section508.22.f | Minor | needs review |
Rules implemented from the axe-core ruleset that don't have an equivalent in the ACT catalogue.
| Implemented | axe-core Rule | Description | Tags | Impact | Issue Type |
|---|---|---|---|---|---|
| ✅ | target-size |
Ensure touch target have sufficient size and space | wcag22aa, sc258 | Serious | failure |
Rules that do not necessarily conform to WCAG success criterion but are industry accepted practices that improve the user experience.
| Implemented | axe-core Rule | Description | Tags | Impact | Issue Type |
|---|---|---|---|---|---|
| ✅ | accesskeys |
Ensures every accesskey attribute value is unique | best-practice | Serious | failure |
| ✅ | aria-allowed-role |
Ensures role attribute has an appropriate value for the element | best-practice | Minor | failure, needs review |
| ✅ | aria-dialog-name |
Ensures every ARIA dialog and alertdialog node has an accessible name | best-practice | Serious | failure, needs review |
| ✅ | aria-text |
Ensures "role=text" is used on elements with no focusable descendants | best-practice | Serious | failure, needs review |
| ✅ | aria-treeitem-name |
Ensures every ARIA treeitem node has an accessible name | best-practice | Serious | failure, needs review |
| ✅ | empty-table-header |
Ensures table headers have discernible text | best-practice | Minor | failure, needs review |
| ✅ | frame-tested |
Ensures <iframe> and <frame> elements have been tested for accessibility | best-practice | Critical | failure, needs review |
| ✅ | heading-order |
Ensures the order of headings is semantically correct | best-practice | Moderate | failure, needs review |
| ✅ | image-redundant-alt |
Ensure image alternative is not repeated as text | best-practice | Minor | failure |
| ✅ | label-title-only |
Ensures that every form element has a visible label and is not solely labeled using hidden labels, or the title or aria-describedby attributes | best-practice | Serious | failure |
| ✅ | landmark-banner-is-top-level |
Ensures the banner landmark is at top level | best-practice | Moderate | failure |
| ✅ | landmark-complementary-is-top-level |
Ensures the complementary landmark or aside is at top level | best-practice | Moderate | failure |
| ✅ | landmark-contentinfo-is-top-level |
Ensures the contentinfo landmark is at top level | best-practice | Moderate | failure |
| ✅ | landmark-main-is-top-level |
Ensures the main landmark is at top level | best-practice | Moderate | failure |
| ✅ | landmark-no-duplicate-banner |
Ensures the document has at most one banner landmark | best-practice | Moderate | failure |
| ✅ | landmark-no-duplicate-contentinfo |
Ensures the document has at most one contentinfo landmark | best-practice | Moderate | failure |
| ✅ | landmark-no-duplicate-main |
Ensures the document has at most one main landmark | best-practice | Moderate | failure |
| ✅ | landmark-one-main |
Ensures the document has a main landmark | best-practice | Moderate | failure |
| ✅ | landmark-unique |
Landmarks should have a unique role or role/label/title (i.e. accessible name) combination | best-practice | Moderate | failure |
| ✅ | meta-viewport-large |
Ensures <meta name="viewport"> can scale a significant amount | best-practice | Minor | failure |
| ✅ | page-has-heading-one |
Ensure that the page, or at least one of its frames contains a level-one heading | best-practice | Moderate | failure |
| ✅ | region |
Ensures all page content is contained by landmarks | best-practice | Moderate | failure |
| ✅ | scope-attr-valid |
Ensures the scope attribute is used correctly on tables | best-practice | Moderate, Critical | failure |
| ✅ | skip-link |
Ensure all skip links have a focusable target | best-practice | Moderate | failure, needs review |
| ✅ | tabindex |
Ensures tabindex attribute values are not greater than 0 | best-practice | Serious | failure |
| ✅ | table-duplicate-name |
Ensure the <caption> element does not contain the same text as the summary attribute | best-practice | Minor | failure, needs review |
Rules we are still testing and developing. They are disabled by default.
| Implemented | axe-core Rule | Description | Tags | Impact | Issue Type |
|---|---|---|---|---|---|
| ✅ | focus-order-semantics |
Ensures elements in the focus order have a role appropriate for interactive content | best-practice, experimental | Minor | failure |
| ✅ | hidden-content |
Informs users about hidden content. | experimental, best-practice | Minor | failure, needs review |
| ✅ | link-in-text-block |
Ensure links are distinguished from surrounding text in a way that does not rely on color | experimental, wcag2a, wcag141 | Serious | failure, needs review |
| ✅ | p-as-heading |
Ensure bold, italic text and font-size is not used to style <p> elements as a heading | wcag2a, wcag131, experimental | Serious | failure, needs review |
| ✅ | table-fake-caption |
Ensure that tables with a caption use the <caption> element. | experimental, wcag2a, wcag131, section508, section508.22.g | Serious | failure |
| ✅ | td-has-header |
Ensure that each non-empty data cell in a <table> larger than 3 by 3 has one or more table headers | experimental, wcag2a, wcag131, section508, section508.22.g | Critical | failure |
Rules implemented in this scanner that are not part of any external rule set. These are not included in the default rule set due to higher false-positive rates. To use them, import and pass them explicitly:
import { Scanner } from "@koddsson/accessibility-scanner";
import errorMessage from "@koddsson/accessibility-scanner/rules/error-message";
const scanner = new Scanner([...allRules, errorMessage]);| Implemented | Rule | Description | Tags | Impact | Issue Type |
|---|---|---|---|---|---|
| ✅ | error-message |
Ensures that error messages are programmatically associated with form fields | wcag2a, wcag331, ACT | Moderate | failure |