The PDF accessibility checker that runs anywhere: PDF/UA-1 and WCAG 2.2, in your terminal, your CI and your browser.
It finds what validators find, and then what they cannot.
pipx install outloud # or: uv tool install outloud
outloud report.pdf # check it
outloud --view # or open the app and drop files inNo Java. No Windows. No upload. Nothing leaves your machine.
The standard tools for checking a PDF's accessibility are PAC, which is a Windows desktop program, and veraPDF, which is a Java validator built for conformance labs. If you are on a Mac, in a CI pipeline, or just want an answer in two seconds, there has been nothing.
And both of them test whether the keys exist. A figure has alternative text: pass. A table has header cells: pass. They cannot tell you that the alternative text is IMG_2041.jpg, that the header cells are empty, or that a sentence on the page is marked as decoration so every screen reader skips it.
This is a published, professionally remediated report. veraPDF passes it. Its appendix table has header cells with nothing in them, so a screen reader announces every value in it against silence:
outloud exists for findings like that one.
Two layers, kept apart. Conformance rules test a requirement of ISO 14289-1 (PDF/UA-1), the same ones veraPDF and PAC test, and are calibrated to agree with veraPDF: on 58 one-defect fixtures and 102 real files, they agree on all 160. Semantic rules test what a conformant file still gets wrong: words printed on the page that no tag announces, prose marked as an artifact, header cells that are empty or shredded one word per row, alternative text that is a file name or LaTeX source, a "formula" that swallowed a sentence, a page painted in a different order from its tags, a title that is the file name. Each names its evidence so a person can judge. No validator runs these.
Every finding tells you what to do. Page, evidence, the ISO clause and Matterhorn checkpoint it rests on, the WCAG 2.2 criteria it affects, and a one-line fix.
Pass and fail by criterion, like PAC's two tabs. Every checkpoint of the Matterhorn Protocol 1.1 (31) and every WCAG 2.2 success criterion at A and AA (55), each with one of five honest statuses: fail, warning, pass, not applicable (no forms in this file, so nothing to label) and needs a person, with a sentence on what to look at. A file without tables does not "pass" the table rules.
See where. Findings carry bounding boxes. The app outlines them on the rendered page, shows the structure tree (click an element to see where it sits), and reads each page back in the order a screen reader would, with artifacts shown as what a reader skips.
Built for pipelines. Exit codes, JSON, SARIF 2.1.0 for GitHub code scanning (with the fix as help text and a WCAG taxonomy), and a single-file HTML report.
outloud file.pdf # findings on the terminal, exit 1 if any error
outloud a.pdf b.pdf reports/ # several files; directories are searched
outloud file.pdf --criteria # add the PDF/UA-1 and WCAG 2.2 tables
outloud out.pdf --source in.pdf # also compare a remediated file with its original
outloud *.pdf --json r.json --sarif r.sarif --html r.html
outloud file.pdf --layer conformance # only what a validator would run
outloud file.pdf --only TBL-011,TAG-003 # or --skip
outloud --list-rulesVerdicts: pass, review (warnings only), fail (at least one error), unreadable. --fail-on warning makes warnings fail the exit status; --fail-on never reports without failing.
Try it on the bundled example, one page with nine different defects:
outloud examples/demo.pdf --viewoutloud --view # opens empty: drop PDFs in, pick them, or type a path
outloud report.pdf --view # opens with that file already checkedA small local server from the Python standard library. It binds to localhost, keeps dropped files in a temporary folder it removes on exit, and stops with Ctrl+C. Deep links work: ?doc=report&tab=crit&fw=wcag22&f=TBL-011.
- run: pipx install outloud
- run: outloud dist/*.pdf --sarif outloud.sarif
- uses: github/codeql-action/upload-sarif@v3
if: always()
with: { sarif_file: outloud.sarif }from outloud import check
result = check("report.pdf")
print(result.verdict, result.errors, result.warnings)
for f in result.sorted_findings():
print(f.rule, f.page, f.message, f.boxes)
for row in result.criteria["wcag22"]:
print(row["id"], row["name"], row["status"])Measured on 17 September 2026 on one machine: veraPDF 1.26.5 (--flavour ua1) and outloud timed per file including start-up; pdfa11y (built from its 25 July 2026 source) timed as one process per corpus, which is how it is meant to run.
On the 21 remediated production files that veraPDF calls compliant, outloud's semantic layer still reports something on 9. Those are the findings the tool is for.
Speed is honest rather than headline: 2 to 10 times faster than veraPDF, mostly because there is no JVM to start, and 5 to 20 times slower than pdfa11y, because the walker is Python and every glyph costs a loop iteration. A compiled core is planned once the rule set settles. pdfa11y is a well-built tool and reading its code made outloud better; where the two disagree, and who we think is right, is written up rule by rule in docs/COMPARISON-pdfa11y.md. Run the comparisons on your own files with scripts/compare_verapdf.py and scripts/compare_pdfa11y.py.
90 rules, 88 implemented, 2 planned and reported as "not run" so the gap is visible. Every rule is data in catalogue.yaml and documented in docs/RULES.md, which is generated from it, along with an index by Matterhorn checkpoint and by WCAG criterion.
model.py reads the file once with pikepdf and resolves what is easy to get wrong: role-mapped types, inherited page references, marked content inside form XObjects, attributes by owner, fonts with their Unicode mapping. content.py walks each page's content stream and records every painted text run with its characters, position and tag. Glyph-by-glyph text operators are merged into words and lines, kerning gaps become spaces, ligatures are normalised, because otherwise no rule can find a word in a file written by Chrome, Word or TeX. Rules read that model and nothing else, and the registry works out once per file which rules apply.
Fixtures are built from nothing by tests/builder.py: a subset TrueType font with a ToUnicode map, marked content, a structure tree with a parent tree, XMP. Every negative test bends exactly one thing in a clean baseline and asserts that one rule fires.
.venv/bin/python -m pytest # 84 tests
.venv/bin/python scripts/make_fixtures.py
.venv/bin/python scripts/make_screenshots.py # regenerates the pictures on this pageAn evaluation, not a certification, and not legal advice. Semantic rules are heuristics; they name what they saw and can be wrong about what it means. A "pass" on a WCAG criterion means the automated rules mapped to it found nothing, not that the criterion is met; the criteria that need a person say so. It does not measure colour contrast yet, tests PDF/UA-1 only, and the Matterhorn failure-condition ids are not yet verified against the protocol text, so the catalogue cites checkpoints.
- Colour contrast (WCAG 1.4.3, 1.4.11)
- PDF/UA-2
- Per-element language (3.1.2)
- A compiled core for the content walker
- A GitHub Action
- Heading levels beyond H6, cross-page reading order
The most useful thing you can send is a PDF that outloud gets wrong. A rule is a catalogue entry, a function and a fixture; CONTRIBUTING.md has the five-minute version.
If outloud found something in your files that a validator passed, a star helps other people find it.
Apache 2.0. Built by Visionably and released so that checking a PDF for accessibility costs nothing and runs anywhere.