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 needsaria-selected. - A custom dropdown built from
divs with norole="listbox"/role="option", noaria-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:
-
npm:
npm install -g aria-reach - GitHub: https://github.com/manichandra/aria-reach
-
CI: GitHub Action β
uses: manichandra/[email protected] -
Browser: the DevTools extension β
<!-- add Chrome Web Store URL after approval --> - Zenodo (DOI): https://doi.org/10.5281/zenodo.20681179
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.
United States
NORTH AMERICA
Related News

Mattress Firm Coupons: Save up to $600
4h ago
π I Built a Dropshipping Automation Pipeline β Here's What I Learned (and What I'd Do Differently)
11h ago
How I Cut My LLM API Bill by 40x: A Freelancer's Migration Story
11h ago
Cursor AI Review 2026: The AI-Native Code Editor
8h ago

Another Model Rewrote My Memories. Here's How I Caught It.
8h ago