npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@koddsson/accessibility-scanner

v0.8.0

Published

An accessibility scanner

Readme

accessibility-scanner

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.

ACT Rules

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 | | :---------- | :----------------------------------------------------------------------------------------------------------------------------------------------- | :------------------ | :------------------------------------------------------------------------ | :--------- | | ✅ | 73f2c2autocomplete 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 |

Axe-core Specific Rules — WCAG 2.0 Level A & AA Rules

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 |

Axe-core Specific Rules — WCAG 2.1 Level A & AA Rules

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 |

Axe-core Specific Rules — Best Practices Rules

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 |

Axe-core Specific Rules — Experimental Rules

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 |

Custom Rules

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 |