aria-reach

June 19, 2026 · View on GitHub

Static analyzer for ARIA anti-patterns in shared component libraries, with npm reach scoring to prioritize the fixes that help the most assistive-technology users.

Most accessibility checkers audit applications. aria-reach targets the layer above them: the shared component libraries (UI kits, editors, media players, form frameworks) whose ARIA defects can propagate into downstream applications. A confirmed upstream fix can benefit many consumers as they adopt the corrected release — so that is where audit effort can pay off most.

aria-reach is the reference implementation of the four-class ARIA anti-pattern taxonomy from the paper "ARIA Anti-Patterns in Shared Component Libraries: A Taxonomy and Force-Multiplied Remediation Strategy for Screen Reader Accessibility" (under review; preprint link forthcoming). Each rule is grounded in a real upstream contribution to a major library.

Try the live browser demo — scan a seeded page and inspect each finding without installing anything.

Install

npm install -g aria-reach   # or: npx aria-reach ...

Build from source (for contributors):

git clone https://github.com/manichandra/aria-reach.git
cd aria-reach && npm install && npm run build
node dist/cli.js scan src/

New here? Follow the worked example in GETTING_STARTED.md.

Scan templates for anti-patterns

aria-reach scan src/                  # .html/.htm + inline Angular templates in .ts/.js
aria-reach scan src/ --json           # machine-readable output

Exit code is 1 when any error-severity finding is reported, so it can gate CI.

Use in CI (GitHub Action)

# .github/workflows/accessibility.yml
name: accessibility
on: [push, pull_request]
jobs:
  aria-reach:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: manichandra/aria-reach@v0.1.2
        with:
          path: src            # file(s)/dir(s) to scan, space-separated
          # json: true         # machine-readable output
          # fail-on-error: false   # report-only, don't fail the job

The job fails when any error-severity ARIA finding is reported. GitHub-hosted runners already include Node ≥ 18.

Angular binding syntax is understood: [attr.aria-hidden]="expr" counts as the attribute being handled, and statically-unknowable bound values are never false-flagged. Inline component templates (template: \…`) are extracted from .ts/.js` sources with line numbers mapped back to the source file — scanning PrimeNG's real library source yields 172 findings across 51 component files.

Scan any live page — runtime mode (framework-agnostic)

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). When a finding or one of its ancestors has a recognized DOM fingerprint, it is labeled with a likely origin (PrimeNG, Angular Material, Quill, Video.js, USWDS, MUI, Ant Design, …). Attribution is heuristic and requires confirmation before proposing an upstream fix. The Angular-specific Class IV rule remains static-only because event bindings are compiled away at runtime.

  • Console snippet: paste dist/aria-reach.browser.js (built via npm run build:browser) into any DevTools console → grouped report + window.ariaReach.scan()/.report()/.summary(). Works in Chrome, Edge, Firefox, and Safari.
  • Browser extension: npm run build:ext, then load browser-extension/ unpacked in Chrome; Safari via Xcode's safari-web-extension-converter. See browser-extension/README.md.

The four anti-pattern classes

ClassAnti-patternWCAG / ARIAGrounding contribution
IDecorative Noise Injection — separators/icons exposed to the accessibility treeSC 1.1.1, 4.1.2PrimeNG breadcrumb separators (primefaces/primeng#19568)
IILive Region Urgency Miscalibration — assertive where polite belongsSC 4.1.3Video.js description tracks (videojs/video.js#9178)
IIIWidget Role Contract Violations — wrong/incomplete role, state, or propertySC 4.1.2, WAI-ARIA APGQuill listbox pattern (slab/quill#4807), Angular Material calendar aria-pressedaria-selected (angular/components#33235)
IVAsync State Desynchronization — submission outruns async validationSC 3.3.1, 3.3.4Angular Forms awaitAsyncValidators (angular/angular#68661)

Rules

RuleClassDefault severity
decorative-separator-aria-hiddenIerror / warning
svg-decorative-aria-hiddenIwarning
assertive-live-region-reviewIIwarning / info
listbox-missing-optionsIIIerror
option-missing-aria-selectedIIIerror
aria-pressed-in-selection-contextIIIerror
haspopup-missing-aria-expandedIIIwarning
ngsubmit-await-async-validatorsIVinfo

Reach scoring (Library Reach Index)

Quantify how far an upstream fix travels before you spend review effort:

aria-reach reach primeng quill video.js @angular/forms @angular/material
Library Reach Index (LRI = weekly downloads x A-hat)

package                 downloads/week   A-hat   LRI (est. deployments)
primeng                      2,012,345     0.1                  201,234
...

LRI(L) = Dw(L) × Â(L) — weekly npm downloads times an estimated deployments-per-download coefficient (--a-hat, default 0.1). The LRI is an order-of-magnitude prioritization instrument, not a precise measurement: libraries with high LRI are the highest-leverage targets for upstream accessibility contribution.

Library API

import { scanPaths, scanSource, reach } from 'aria-reach';

const findings = scanPaths(['src/']);            // Finding[]
const inline = scanSource('<span>›</span>', 'x.html');
const rows = await reach(['primeng'], 0.1);      // ReachRow[]

Limitations (read this)

Static analysis sees annotated-but-wrong patterns; it cannot prove a custom widget lacking all semantics is interactive, cannot evaluate bound expressions, and cannot replace runtime checkers (axe-core) or manual screen-reader testing (NVDA/JAWS/VoiceOver). Class IV detection is a heuristic: it flags the opportunity for desynchronization, not a proven defect. Use aria-reach as the library-layer complement to — never a substitute for — application-layer audits and AT testing.

Framework support

SurfaceStatic CLIRuntime (snippet/extension)
Plain HTML
Angular templates (external + inline, binding syntax)✅ (rendered)
React (JSX), Vue (SFC), Svelte⏳ roadmap✅ (rendered DOM)
Any page in a browser

The taxonomy is framework-agnostic (two of the five grounding case studies — Video.js and Quill — are vanilla JS). The static CLI started Angular-first because that ecosystem hosts the grounding contributions; runtime mode covers everything else today.

Roadmap

  • Chrome DevTools panel (inspect-element integration; Safari lacks the devtools WebExtensions API, so Safari keeps the popup)
  • Dependency-graph propagation: enumerate downstream consumers affected by a library-level finding ("npm audit for accessibility")
  • Class IV control-flow analysis of TypeScript component sources (async validator registration vs. submit paths)
  • React/JSX, Vue SFC, and Svelte template extraction for the static CLI
  • SARIF output for CI annotation

Author

Manichandra Sajjanapu — personal open-source project; not affiliated with or representing any employer. MIT licensed. Contributions welcome.