The first 508 audit that actually listens.
aloud collects accessibility evidence for mobile and web apps: screen-reader transcripts, accessibility checks, screenshots, HTML reports, and draft OpenACR documents. Each platform labels how speech was captured or computed; web checks run without a screen reader by default.
- Android: real TalkBack, built from Google's source at a pinned commit. The transcript is what TalkBack spoke, captured from its speech log.
- iOS: computed VoiceOver transcripts by default. Opt-in real speech
through Xcode 27's
XCUIVoiceOverServiceuses--voiceover real --no-gateand labels traversal as partial. - Web (experimental): Chromium page checks, screenshots, and structural snapshots, with an opt-in NVDA command-capture adapter for Windows and an opt-in Safari + VoiceOver adapter validated on disposable GitHub-hosted macOS runners. Web evidence is report-only. Setup and validation status.
Around the capture:
- One rule catalog. Every tree rule's platform, severity, WCAG criteria,
and a plain statement of what it checks live in
src/rules/catalog.mjs(@irs-public/aloud/rules). The rule engines and the OpenACR draft both read it, so they cannot disagree. - WCAG 2.5.8 at its real minimum. Target size fails under 24dp/24pt, with the spacing exception; Android's 48dp and Apple's 44pt guidelines are warnings, not failures.
- Baselines with reasons. A ratchet gate: per-screen error counts can only go down. Each accepted error can record why it stands (a product bug, a platform gap, or an accepted risk) and the reason carries into the report and the draft.
- Provenance. Every evidence file records the commit, machine, CI run,
and tool versions it came from; reports refuse to combine evidence from
different runs unless told to (
--allow-mixed). - OpenACR from any findings. One engine turns a findings document from
any source (aloud's audits, the USWDS accessibility harness,
a manual review) into a draft OpenACR that lists every criterion and
never shows a silent pass:
aloud acr, a library, and a GitHub Action. - Shared screen-reader drivers.
@irs-public/aloud/web/nvdaand/web/voiceoverstart, command, and stop NVDA and VoiceOver for browser tests. The USWDS accessibility harness imports them at a reviewed commit for its own screen-reader suites.
Both mobile audit legs have passed real CI runs on the IRS mobile app project it was built for. Browser validation covers Chromium fixtures and the public TodoMVC React app, including repeated NVDA runs on Windows, and the Safari + VoiceOver fixtures on GitHub-hosted macOS runners.
508 audit tools today do not listen. Static scanners check the accessibility tree and stop there. But the DHS Trusted Tester methodology for mobile names its test instruments plainly: VoiceOver and TalkBack. A conformant audit uses the screen reader itself. aloud automates exactly that. It runs the instrument, records the speech, and turns the evidence into a report.
One more reason the name fits: "audit" comes from the Latin audire, to hear. The first audits were hearings. This one is too.
No emulator, no simulator, no setup. The demo replays a captured audit of a bundled sample screen through the real pipeline:
git clone https://github.com/IRS-Public/aloud && cd aloud && npm install
npx aloud demoHere is the demo screen's captured TalkBack transcript:
Order status, heading
Your order shipped on Tuesday, August 25th.
Track package, button
Unlabeled, button
Cancel order, button
Line four is the audit. A static scanner logs "missing contentDescription" in a table. aloud shows you the moment a blind user reaches a share button and hears the word "Unlabeled". The demo report fails that screen on one real finding, the unlabeled control. It also warns that the 32dp cancel button is under Android's 48dp touch-target guideline. That is a warning, not a failure, because the button meets WCAG 2.5.8's 24dp minimum. If your machine has a text-to-speech voice, the report also reconstructs the transcript as playable audio, labeled as a reconstruction.
Any tool that can say, per criterion, what its evidence shows can get a
draft OpenACR. Write a findings document (see
examples/findings.example.json), then:
npx aloud acr --findings node_modules/@irs-public/aloud/examples/findings.example.json --out acr-draft.yamlThe package ships the example, so that command works in any project that
has @irs-public/aloud installed. From a checkout of this repository, run
node bin/aloud.mjs acr --findings examples/findings.example.json --out acr-draft.yaml
instead. For your own evidence, pass your findings file.
The draft lists every criterion in the WCAG 2.2 / Section 508 catalog.
Findings set levels through a conservative policy: only passing evidence
can reach supports, a failure is partially-supports or
does-not-support, and anything unproven, or not covered by a finding,
is not-evaluated for a person to review. Unknown criteria, statuses, or
fields fail the command with every problem listed, and nothing is
written. In code:
import { buildAcr, toYaml } from "@irs-public/aloud";
// The report date comes from findings.provenance.date or options.date; the
// library throws without one (the CLI falls back to today).
const yaml = toYaml(buildAcr(findings, { date: "2026-09-30" }));The contract, the status vocabulary, the policy and how to override it, and the checklist for finishing a draft are in docs/openacr.md. The harness integration guide shows a complete adapter from the USWDS accessibility harness's report.
aloud is a standalone CLI. Point it at a mobile app build or a running web app. You do not need CI or changes to your app. For a URL, jump to Web apps.
For mobile audits, install it as in the demo above, then write a small config
(see examples/aloud.config.example.json). Save it as aloud.config.json in
your project root.
{
"app": {
"name": "Example App",
"version": "1.0.0",
"android": { "package": "com.example.app", "apk": "build/app-debug.apk" },
"ios": { "bundleId": "com.example.app", "app": "build/Example.app" }
}
}For explicit radio activation and per-action state/speech assertions, see Android interaction evidence.
You need a running emulator with a google_apis (userdebug) image, and a
TalkBack APK (Google ships no prebuilt one, so aloud builds it from source
at a pinned commit and caches it):
npx aloud talkback get --build
npx aloud talkback install .aloud-cache/talkback.apk
npx aloud android --apk path/to/app-debug.apk
open aloud-report/android/index.htmlOn an x86_64 emulator, install .aloud-cache/talkback-nolib.apk instead.
The pinned TalkBack build ships ARM native libs only.
The first run exits with a failure: findings gate against a baseline, and no screen is in one yet. Accept the current counts:
npx aloud baseline aloud-report/androidThe gate is a ratchet: from that baseline, per-screen error counts can only
go down, and a rule id the baseline has never seen fails. Pass --no-gate
to skip the gate instead.
Record why a baselined error stands, so the report and the OpenACR draft explain it (it still counts as a failure):
npx aloud baseline aloud-report/android --accept home:native-interactive-unlabeled \
--kind product-bug --summary "The close button has no label" --issue APP-12Kinds are product-bug, platform-gap, and accepted-risk. --prune
drops screens the run did not cover. See
accepted findings.
You need macOS, a booted simulator, and idb (accessibility tree dumps):
npx aloud ios --app path/to/YourApp.app
open aloud-report/ios/index.htmlSame first-run rule as Android: accept the baseline with
npx aloud baseline aloud-report/ios, or pass --no-gate.
To also collect Apple's per-screen accessibility audit, install xcodegen
and use a configured Xcode 15+ with an iOS 17+ simulator:
brew install xcodegen
npx aloud ios --apple-audit --no-gateThis opt-in integration adds contrast, hit-region, text-clipping, and other Apple findings to the evidence page. Findings are report-only while the integration is calibrated; they do not change the tree-check gate or OpenACR conformance levels. A failed or incomplete native capture stops the run. See docs/ios.md.
Start your web app, then capture its URL with Chromium. The default mode needs no emulator, simulator, or screen reader. Browser dependencies are optional for mobile-only installs.
npm install --save-dev playwright@1.63.0 @axe-core/playwright@4.13.0
npx playwright install chromium
npx aloud web --url http://127.0.0.1:3000
open aloud-report/web/index.htmlThe default captures page structure, axe findings, and screenshots without generating speech. Use the web config and scenario manifest to capture named page states and assert keyboard focus.
| Mode | Runs on | Browser | Captured screen-reader output |
|---|---|---|---|
| Default structural audit | macOS, Windows, or Linux | Chromium | None |
--screen-reader nvda |
Dedicated Windows desktop | Chromium | NVDA through Guidepup |
--screen-reader voiceover |
Disposable GitHub-hosted macOS runner (opt-in) | Safari | VoiceOver through Guidepup |
On a dedicated Windows desktop, --screen-reader nvda adds Guidepup-formatted
NVDA command output and speech assertions. Follow the
NVDA setup instructions first. Chromium
fixtures and the external TodoMVC scenarios have passed repeated runs,
including NVDA on Windows; recorded results and limits
describe the tested scope.
--screen-reader voiceover drives Safari and VoiceOver instead. It runs only
on a disposable GitHub-hosted macOS runner that opts in. Its hosted fixture
workflow has passed repeatedly; see
VoiceOver command capture.
Web evidence is report-only: browser baselines and regression gates are
disabled, and every web OpenACR component remains not-evaluated.
Run this after the baseline step: with no flags, openacr reads the
baselines you accepted above.
npx aloud openacrTo draft from a fresh run without a baseline, pass the report dirs:
npx aloud openacr --report aloud-report/android --report-ios aloud-report/ios.
For web evidence, configure app.name and app.version, then run
npx aloud openacr --report-web aloud-report/web. This can be combined with
the mobile report inputs; web criteria remain unevaluated.
Every evidence file records the commit, machine, CI run, and tool versions
it came from, and the draft's notes state them. Reports and drafts refuse
to combine evidence from different commits unless you pass --allow-mixed.
This emits acr-draft.yaml, a machine-readable accessibility conformance
report in the GSA OpenACR format. It is a
draft on purpose: only criteria the automated rules cover get a conformance
level, and every note says so. It is built by the same engine as
aloud acr. See
docs/openacr.md.
In code, the same engine is the package's main entry:
import { buildAcr, toYaml } from "@irs-public/aloud". The package also
exports @irs-public/aloud/rules (the rule catalog) and the web drivers
(/web/nvda, /web/voiceover, /web/dependencies); deep src/, bin/,
and examples/ paths keep working. See docs/openacr.md.
For mobile apps, aloud audits whatever screen is currently open
(--nav current-screen, the default). Give it a screens manifest
(examples/screens-deeplinks.example.json) to walk your whole app by deep
links, or use bridge mode (examples/screens-bridge.example.json) for apps
with a dev navigation hook. See
docs/how-it-works.md.
aloud runs fine on GitHub-hosted runners. Copy the templates in
examples/ci/:
examples/ci/android-audit.yml: boots a headless emulator with KVM, builds TalkBack once and caches it, runs the audit, uploads the evidence page.examples/ci/ios-audit.yml: boots a simulator on a macOS runner, installsidbfrom a pinned tarball, runs the audit, uploads the evidence page.
Both templates use only GitHub-owned actions. See docs/ci.md.
To draft an OpenACR from a findings file in any repository's workflow, use
the aloud acr action in this repository, pinned to a commit:
- id: acr
uses: IRS-Public/aloud@<full commit sha>
with:
findings: findings.json # required
out: acr.yaml # default
- run: echo "Draft at ${{ steps.acr.outputs.acr }}"It adds a count of the draft's conformance levels to the job summary. See docs/ci.md, and the harness integration guide for how the USWDS accessibility harness drafts one ACR per component in its own workflow.
The repository's browser acceptance workflow runs Chromium fixtures on relevant pull requests. Manual dispatch adds the external web-app scenarios and can enable Windows NVDA validation. The VoiceOver workflow runs the Safari + VoiceOver fixtures on manual dispatch and on pull requests that touch the Safari driver.
Android runs two passes per screen. Pass one: TalkBack is on, and
logcat markers bracket each screen so TalkBack's verbose speech log can be
sliced into a per-screen spoken transcript. Pass two: TalkBack is off, and
uiautomator dump feeds the 508 rule checks plus an evidence screenshot.
Two passes because uiautomator evicts running accessibility services, so
the tree pass would kill TalkBack mid-speech.
Opt-in aloud android --atf --pass tree --no-gate captures richer native nodes
through the companion and runs six pinned Google Accessibility Test Framework
checks on the same snapshot. Native findings are report-only; skipped checks
remain explicit. See setup and coverage.
iOS runs one pass. idb dumps the accessibility tree per screen. aloud
computes the VoiceOver utterance for each element (label, value, trait,
hint) and runs the iOS rule checks. The transcript is labeled
computed-voiceover. Opt-in --voiceover real --no-gate records actual
speech with source: "voiceover", raw capture evidence, and explicit partial
coverage. Partial speech cannot pass the gate. Requirements and screen-state
limits are documented in docs/ios.md.
Web captures configured Chromium page states with Playwright, runs axe-core, and retains ARIA snapshots, screenshots, and keyboard/focus assertions. Opt-in NVDA adds captured command output; opt-in VoiceOver drives Safari through Apple Events on a disposable macOS runner. Run inventories and artifact hashes are verified when reports are regenerated. Scripted scenario completion does not establish full traversal or conformance; see web coverage.
Mobile tree-check findings gate against a per-screen baseline you accept with
aloud baseline. It is a ratchet: counts only go down, and a rule id the
baseline has never seen fails even under the count.
Full pipeline, rule tables, and known limits: docs/how-it-works.md.
aloud's transcripts were read against a real IRS-app feature branch after the tree checks had passed every screen. Three findings surfaced. Chasing each to root cause improved the tool more than the app, which is the honest shape of dogfooding:
- "Paperless notices, 1." Preference toggles appeared to announce a raw
numeric state. Root cause: a UISwitch dumps
AXValue "1"through the mac-AX bridge, but real VoiceOver speaks "on". The transcript was wrong, not the app. Fixed: switch-family numeric state now normalizes to on/off, and RN's Switch (which surfaces asCheckBox) is recognized as a switch. A newios-toggle-raw-valuewarning covers the genuinely broken shape, a non-switch control wearing numeric state, and stays out of text fields and sliders, where a "1" is content. - Roster rows intermittently losing their button trait. In walk-time
dumps, two of six visually identical client rows read as static text; in
settled dumps all six carry the trait. The app's code was right. The
dump can race the accessibility tree's realization. Fixed at the cause:
the walker now re-dumps until two consecutive dumps agree. The new
ios-list-row-not-interactivewarning still flags a static row sitting in a column of interactive siblings, so a real lost trait gets human review instead of a silent pass. - A switch flagged for being 51x31pt. Adding CheckBox to the interactive set tripped the 44pt target rule on Apple's own UISwitch geometry. Apple's audit passes it; aloud now exempts switch-family roles from both target-size rules.
The meta-lesson is the tool's thesis restated: the dump is not the speech. Every one of these was invisible to a static tree check and surfaced only by reading what VoiceOver would say.
Working today, proven in CI:
- Android TalkBack transcripts and tree checks, two-pass.
- iOS computed VoiceOver transcripts and tree checks.
- Experimental Chromium checks and keyboard/focus scenarios, with opt-in NVDA command capture validated on Windows and the external TodoMVC React app.
- Mobile ratchet gate, HTML evidence pages, and draft OpenACR emitter.
New in 0.2.0 (unreleased; see the changelog):
- The shared findings -> OpenACR engine (
aloud acr, the library, and the GitHub Action), with a conservative level policy, astandard-interpretationstatus (4.1.1 Parsing on HTML), and a harness adapter that follows the harness's current report statuses. - One rule catalog, WCAG 2.5.8 at its 24-unit minimum, accepted baseline reasons, and provenance on every evidence file.
- An experimental Safari + VoiceOver web driver, passing its fixtures on hosted macOS runners (recorded run).
- The NVDA and VoiceOver drivers as library exports, carrying the USWDS harness's refinements; the harness runs its screen-reader suites through them.
Roadmap, in implementation order:
- Per-screen Apple accessibility audits. The opt-in
--apple-auditintegration is available for validation on iOS 17+. Keep native findings report-only until real-app fixtures establish useful severity and coverage. This work does not depend on the VoiceOver beta API. - Real VoiceOver on iOS. Opt-in per-screen capture preserves raw speech, target identity, and explicit partial coverage. A verified completion signal, complete utterance text, and broader released-runtime validation remain required before it becomes the default. Today's baselines store tree errors, so no transcript migration is needed. Tracking #23.
- TalkBack focus stepping. Opt-in
--talkback focusdrives the pinned TalkBack gesture pipeline and captures focus and speech requests, including scrolling and duplicate labels. Native backward/forward boundaries are required; incomplete traversal fails. Setup and limits. - A logging TTS engine. Opt-in
--talkback focus --tts loggingverifies durable requests against engine receipts and queue/completion events. The test engine emits synthetic silence; request accounting does not prove audible delivery. Setup and evidence. - Deeper Android checks. Opt-in
--atfcaptures native hints, state descriptions, pane titles, and role-description extras, and runs six ATF 4.1.1 checks. Reports preserve exact element identities, skipped results, and overlap with tree findings. New results remain report-only. Setup and coverage. - Web app evidence. Experimental
aloud webuses pinned Chromium and axe-core, with separate NVDA and Safari + VoiceOver command-capture adapters. Repeated Windows and hosted macOS fixtures validate scripted command capture; browser gates still require their own coverage policy. Scope and validation.
Acceptance criteria and dependencies: technical roadmap. External app results and limits: Wikipedia mobile validation and TodoMVC browser validation.
Static scanners (axe, Accessibility Scanner, Xcode's audit) inspect markup or the accessibility tree. They are useful, and aloud runs tree checks too. But they do not run a screen reader, so they cannot tell you what a blind user hears. Projects like ARIA-AT drive real screen readers to test interoperability on the web, not to audit an app. Commercial screen-reader automation exists in beta. As far as we know, no open tool before aloud combined all four: crawl an app's screens, drive the real screen readers, assert on the captured speech, and emit 508/OpenACR reporting. If we are wrong, open an issue; we would genuinely like to know.
| aloud | Static scanners (axe, Accessibility Scanner) | Xcode audit | ARIA-AT | Commercial beta tools | |
|---|---|---|---|---|---|
| Runs the real screen reader | Yes* | No | No | Yes | Yes |
| Captures the spoken output | Yes* | No | No | Yes | Yes |
| Walks every screen of a mobile app | Yes | No | Only screens your UI tests visit | No, web only | Varies |
| Section 508 / OpenACR reporting | Yes | No | No | No | No |
| Open source | Yes, CC0 | Core only | No | Yes | No |
* Real TalkBack on Android today. On iOS the default transcript is computed and labeled as computed; opt-in real VoiceOver captures are partial on Xcode 27 GA, with the experimental harness already in this repo. Web defaults to structural checks; opt-in NVDA (and experimental Safari + VoiceOver) preserve Guidepup-formatted command output and remain report-only.
This project is dedicated to the public domain under CC0 1.0 Universal. It was developed as a work of the United States federal government, following the precedent of IRS Direct File. You may copy, modify, and use it, for any purpose, without permission or attribution.
See CONTRIBUTING.md and SECURITY.md.
