Fetching latest headlinesโ€ฆ
One ARIA bug, a million apps: meet aria-reach
NORTH AMERICA
๐Ÿ‡บ๐Ÿ‡ธ United Statesโ€ขJuly 6, 2026

One ARIA bug, a million apps: meet aria-reach

0 views0 likes0 comments
Originally published byDev.to

Most accessibility tools audit your application. But in a modern JavaScript app, the accessibility of what ships is mostly decided one layer down โ€” in the shared component libraries you import: the UI kit, the rich-text editor, the media player, the form framework.

That layer is where accessibility bugs scale. A single misconfigured aria-* attribute in a popular library can propagate into downstream apps that import it. A wrong aria-live value in a media player can affect sites that embed it. The flip side is the opportunity: a confirmed upstream fix can benefit many consumers as they adopt the corrected release.

aria-reach is a small open-source analyzer built around that idea. It scans for ARIA anti-patterns in component libraries (and in any live page), attributes findings to their likely upstream source, and โ€” the part I haven't seen elsewhere โ€” ranks them by reach, so you spend effort where it helps the most assistive-technology users.

npm install -g aria-reach
aria-reach scan src/

The four classes of anti-pattern

aria-reach organizes findings into a four-class taxonomy. Each class is grounded in a real, concrete defect pattern from a widely used library.

Class I โ€” Decorative Noise Injection. Decorative elements leak into the accessibility tree and get announced as content. Classic case: breadcrumb separators (/, โ€บ) read aloud between every link. The fix is aria-hidden="true" on the decorative node.

Class II โ€” Live-Region Urgency Miscalibration. Routine, non-urgent updates announced with aria-live="assertive" (or role="alert"), which interrupts whatever the screen reader is currently saying. A media player that re-announces description text on every update becomes unusable. Routine status belongs in a polite live region (aria-live="polite" / role="status"); assertive is for genuinely time-critical alerts.

Class III โ€” Widget Role Contract Violations. A widget breaks the W3C role contract that assistive tech relies on. Two common shapes:

  • A calendar day cell using aria-pressed โ€” so a screen reader announces "button pressed" instead of "selected." A day in a grid is a selection, so it needs aria-selected.
  • A custom dropdown built from divs with no role="listbox"/role="option", no aria-selected, and no keyboard support โ€” invisible to AT as a selectable list.

Class IV โ€” Async State Desynchronization. The accessibility state and the actual state drift apart across async boundaries โ€” e.g., a form submit firing before async validators resolve, so the user is told the form is valid when it isn't.

Each rule maps to a specific WAI-ARIA 1.2 / WCAG 2.1 success criterion (mostly SC 4.1.2 Name, Role, Value and SC 4.1.3 Status Messages), and each is grounded in a real upstream contribution.

What makes it different: reach scoring

Finding ARIA issues is the easy part; prioritizing them is the hard part. axe-core and friends give you a flat list per page. aria-reach weights each finding by a Library Reach Index โ€” roughly weekly npm downloads ร— estimated downstream deployments โ€” so a defect in a library pulled millions of times a week outranks a one-off in your own code. You fix the things that touch the most people first.

It also attributes findings to their likely origin (PrimeNG, Angular Material, Quill, Video.js, USWDS, MUI, โ€ฆ), so a finding becomes an upstream fix opportunity โ€” the force multiplier โ€” rather than a local patch.

Two ways to scan

Static โ€” templates, including Angular inline templates extracted from .ts/.js with line numbers mapped back to source. It understands Angular binding syntax ([attr.aria-hidden]="expr" counts as handled, and unknowable bound values are never false-flagged):

aria-reach scan src/            # .html + inline Angular templates
aria-reach scan src/ --json     # machine-readable, for CI

The CLI exits 1 on any error-severity finding, so it can gate a CI job. As a worked example, scanning PrimeNG's own library source surfaces 172 candidate findings across 51 component files โ€” a useful map of where to look (you still validate a sample by hand; the goal is to point effort, not to auto-merge).

Drop it into CI with the GitHub Action โ€” it fails the build on error-severity findings:

- uses: manichandra/[email protected]
  with:
    path: src              # fail-on-error: false for report-only

Runtime โ€” the runtime-detectable rules (Classes Iโ€“III) run against the rendered DOM of any app (React, Vue, Angular, vanilla โ€” at runtime it's all DOM). Recognized DOM fingerprints provide heuristic, likely-origin labels that require confirmation. Paste the built browser bundle into a DevTools console, or load the browser extension:

ariaReach.scan();     // grouped report in the console
ariaReach.summary();  // counts by class + reach

Try it (and break it)

It's MIT-licensed, Node โ‰ฅ 18, and archived with a DOI so it's citable:

It's the reference implementation of an ARIA anti-pattern taxonomy I'm writing up in a paper (under review; preprint to follow). If you maintain a component library, I'd love a scan result or a counter-example that breaks a rule โ€” open an issue or a PR. The most useful thing you can do is point it at a library you depend on, confirm the highest-reach finding, and propose the fix upstream.

Built and maintained on personal time. Feedback and contributions welcome.

Comments (0)

Sign in to join the discussion

Be the first to comment!