{"id":51377194,"url":"https://github.com/manichandra/aria-reach","last_synced_at":"2026-07-05T21:00:32.350Z","repository":{"id":365820193,"uuid":"1268066457","full_name":"manichandra/aria-reach","owner":"manichandra","description":null,"archived":false,"fork":false,"pushed_at":"2026-06-19T03:52:04.000Z","size":68,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-19T04:16:58.293Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/manichandra.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":".zenodo.json","notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-13T05:29:23.000Z","updated_at":"2026-06-19T03:52:07.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/manichandra/aria-reach","commit_stats":null,"previous_names":["manichandra/aria-reach"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/manichandra/aria-reach","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/manichandra%2Faria-reach","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/manichandra%2Faria-reach/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/manichandra%2Faria-reach/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/manichandra%2Faria-reach/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/manichandra","download_url":"https://codeload.github.com/manichandra/aria-reach/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/manichandra%2Faria-reach/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35168795,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-05T02:00:06.290Z","response_time":100,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-07-03T14:00:27.941Z","updated_at":"2026-07-05T21:00:32.345Z","avatar_url":"https://github.com/manichandra.png","language":"JavaScript","funding_links":[],"categories":["Development Utilities"],"sub_categories":["Accessibility"],"readme":"# aria-reach\n\nStatic analyzer for **ARIA anti-patterns in shared component libraries**, with npm **reach scoring** to prioritize the fixes that help the most assistive-technology users.\n\nMost 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.\n\n`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.\n\n**[Try the live browser demo](https://manichandra.github.io/aria-reach/)** — scan a seeded page and inspect each finding without installing anything.\n\n## Install\n\n```bash\nnpm install -g aria-reach   # or: npx aria-reach ...\n```\n\n**Build from source** (for contributors):\n```bash\ngit clone https://github.com/manichandra/aria-reach.git\ncd aria-reach \u0026\u0026 npm install \u0026\u0026 npm run build\nnode dist/cli.js scan src/\n```\n\n**New here? Follow the worked example in [GETTING_STARTED.md](GETTING_STARTED.md).**\n\n## Scan templates for anti-patterns\n\n```bash\naria-reach scan src/                  # .html/.htm + inline Angular templates in .ts/.js\naria-reach scan src/ --json           # machine-readable output\n```\n\nExit code is `1` when any error-severity finding is reported, so it can gate CI.\n\n### Use in CI (GitHub Action)\n\n```yaml\n# .github/workflows/accessibility.yml\nname: accessibility\non: [push, pull_request]\njobs:\n  aria-reach:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: manichandra/aria-reach@v0.1.2\n        with:\n          path: src            # file(s)/dir(s) to scan, space-separated\n          # json: true         # machine-readable output\n          # fail-on-error: false   # report-only, don't fail the job\n```\n\nThe job fails when any error-severity ARIA finding is reported. GitHub-hosted runners already include Node ≥ 18.\n\nAngular 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.\n\n## Scan any live page — runtime mode (framework-agnostic)\n\nThe 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.\n\n- **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.\n- **Browser extension:** `npm run build:ext`, then load [browser-extension/](browser-extension/) unpacked in Chrome; Safari via Xcode's `safari-web-extension-converter`. See [browser-extension/README.md](browser-extension/README.md).\n\n## The four anti-pattern classes\n\n| Class | Anti-pattern | WCAG / ARIA | Grounding contribution |\n|---|---|---|---|\n| **I** | Decorative Noise Injection — separators/icons exposed to the accessibility tree | SC 1.1.1, 4.1.2 | PrimeNG breadcrumb separators ([primefaces/primeng#19568](https://github.com/primefaces/primeng/pull/19568)) |\n| **II** | Live Region Urgency Miscalibration — `assertive` where `polite` belongs | SC 4.1.3 | Video.js description tracks ([videojs/video.js#9178](https://github.com/videojs/video.js/pull/9178)) |\n| **III** | Widget Role Contract Violations — wrong/incomplete role, state, or property | SC 4.1.2, WAI-ARIA APG | Quill listbox pattern ([slab/quill#4807](https://github.com/slab/quill/pull/4807)), Angular Material calendar `aria-pressed`→`aria-selected` ([angular/components#33235](https://github.com/angular/components/pull/33235)) |\n| **IV** | Async State Desynchronization — submission outruns async validation | SC 3.3.1, 3.3.4 | Angular Forms `awaitAsyncValidators` ([angular/angular#68661](https://github.com/angular/angular/pull/68661)) |\n\n### Rules\n\n| Rule | Class | Default severity |\n|---|---|---|\n| `decorative-separator-aria-hidden` | I | error / warning |\n| `svg-decorative-aria-hidden` | I | warning |\n| `assertive-live-region-review` | II | warning / info |\n| `listbox-missing-options` | III | error |\n| `option-missing-aria-selected` | III | error |\n| `aria-pressed-in-selection-context` | III | error |\n| `haspopup-missing-aria-expanded` | III | warning |\n| `ngsubmit-await-async-validators` | IV | info |\n\n## Reach scoring (Library Reach Index)\n\nQuantify how far an upstream fix travels before you spend review effort:\n\n```bash\naria-reach reach primeng quill video.js @angular/forms @angular/material\n```\n\n```\nLibrary Reach Index (LRI = weekly downloads x A-hat)\n\npackage                 downloads/week   A-hat   LRI (est. deployments)\nprimeng                      2,012,345     0.1                  201,234\n...\n```\n\n`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.\n\n## Library API\n\n```js\nimport { scanPaths, scanSource, reach } from 'aria-reach';\n\nconst findings = scanPaths(['src/']);            // Finding[]\nconst inline = scanSource('\u003cspan\u003e›\u003c/span\u003e', 'x.html');\nconst rows = await reach(['primeng'], 0.1);      // ReachRow[]\n```\n\n## Limitations (read this)\n\nStatic 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.\n\n## Framework support\n\n| Surface | Static CLI | Runtime (snippet/extension) |\n|---|---|---|\n| Plain HTML | ✅ | ✅ |\n| Angular templates (external + inline, binding syntax) | ✅ | ✅ (rendered) |\n| React (JSX), Vue (SFC), Svelte | ⏳ roadmap | ✅ (rendered DOM) |\n| Any page in a browser | — | ✅ |\n\nThe 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.\n\n## Roadmap\n\n- Chrome DevTools panel (inspect-element integration; Safari lacks the devtools WebExtensions API, so Safari keeps the popup)\n- Dependency-graph propagation: enumerate downstream consumers affected by a library-level finding (\"npm audit for accessibility\")\n- Class IV control-flow analysis of TypeScript component sources (async validator registration vs. submit paths)\n- React/JSX, Vue SFC, and Svelte template extraction for the static CLI\n- SARIF output for CI annotation\n\n## Author\n\nManichandra Sajjanapu — personal open-source project; not affiliated with or representing any employer. MIT licensed. Contributions welcome.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmanichandra%2Faria-reach","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmanichandra%2Faria-reach","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmanichandra%2Faria-reach/lists"}