The WCAG 2.2 accessibility checks axe-core can't do: focus obscuring, focus appearance, target size, dragging, accessible authentication.
Project description
Sightline
axe-core tests 1 of the 9 success criteria WCAG 2.2 added. Sightline tests 8.
Sightline is an accessibility auditor for the WCAG 2.2 criteria that static DOM analysis structurally cannot answer: whether a sticky header covers the focused element, whether a focus indicator is actually visible, whether a control is drag-only, whether a password field silently blocks paste.
It drives a real browser through Playwright, measures pixels and geometry, and reports the numbers behind every verdict.
The problem
Almost every automated accessibility check in the industry runs on
axe-core. Lighthouse depends on it
(axe-core: ^4.12.1),
so does Pa11y, and so
do jest-axe, @axe-core/playwright and axe DevTools by construction.
axe-core injects a script into the page and inspects the DOM and computed styles. That is the right tool for alt text, ARIA validity, heading order and text contrast — and it is excellent at them.
But WCAG 2.2 (W3C Recommendation, revised 12 December 2024) added nine success criteria, and most of them are not questions the DOM can answer. They are questions about layout geometry, rendered pixels, which event handlers a component registered, and how several pages compare to one another.
Check it yourself. Every WCAG tag axe-core emits is listed in
doc/rule-descriptions.md:
curl -s https://raw.githubusercontent.com/dequelabs/axe-core/develop/doc/rule-descriptions.md \
| grep -oE "wcag[0-9a-z]+" | sort -u
The complete set contains exactly one WCAG 2.2 tag: wcag258, on the single
target-size rule, marked "needs review". There is no wcag2411, no
wcag2413, no wcag257, no wcag326, no wcag337, no wcag338.
So a team can have a green accessibility pipeline and still fail eight of the nine criteria the standard added — including four at Level AA, which the European Accessibility Act regime and EN 301 549 both point at.
That gap is what Sightline fills. It is not a replacement for axe-core. Run both.
What it checks
| SC | Criterion | Level | In axe-core? | How Sightline decides |
|---|---|---|---|---|
| 2.4.11 | Focus Not Obscured (Minimum) | AA | ❌ | Tabs through the page for real; at each stop hit-tests a grid of points with elementFromPoint and reports the fraction covered |
| 2.4.12 | Focus Not Obscured (Enhanced) | AAA | ❌ | Same measurement, fails on any real coverage |
| 2.4.13 | Focus Appearance | AAA | ❌ | Screenshots each focus stop unfocused and focused, decodes and diffs the pixels, checks area against a 2px perimeter and contrast against 3:1 |
| 2.5.7 | Dragging Movements | AA | ❌ | Wraps addEventListener before navigation to see which elements registered a pointer-down/pointer-move pair with no keyboard path |
| 2.5.8 | Target Size (Minimum) | AA | ⚠️ one rule, "needs review" | 24×24 px, with the spacing exception implemented as real 24px-circle intersection and the inline exception requiring genuine non-target text |
| 3.2.6 | Consistent Help | A | ❌ | Locates help mechanisms in twelve languages across several pages and compares relative order and containing landmark |
| 3.3.8 | Accessible Authentication (Min) | AA | ❌ | Dispatches a real cancelable paste event to detect paste blocking; checks credential autocomplete; detects CAPTCHAs |
| 3.3.9 | Accessible Authentication (Enhanced) | AAA | ❌ | Same, without the object-recognition exception |
| 3.3.7 | Redundant Entry | A | ❌ | Deliberately not implemented — see Honest limitations |
Install
pip install sightline-a11y
python -m playwright install chromium
Or from source:
git clone https://github.com/Ax1zz/sightline.git
cd sightline
pip install -e ".[dev]"
python -m playwright install chromium
Quick start
# Audit a page
sightline https://example.com
# Level AA only, with a standalone HTML report you can attach to an audit
sightline https://example.com --level AA --html report.html
# Several pages: this additionally enables 3.2.6 Consistent Help
sightline https://example.com/ https://example.com/pricing https://example.com/contact
# Machine-readable output for CI
sightline https://example.com --json - | jq '.counts'
Exit code is 1 when anything fails, 0 otherwise, so it drops straight into a
pipeline. --exit-zero reports without failing the build;
--fail-on-review is stricter and also fails on review items.
Use it inside an existing Playwright test
The CLI navigates for you. If you already have a page — behind a login, mid-flow, three modals deep — hand it over directly:
from playwright.sync_api import sync_playwright
from sightline import audit, instrument
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
instrument(page) # before navigating, for 2.5.7
page.goto("https://app.example.com/login")
page.fill("#email", "demo@example.com")
page.fill("#password", "hunter2")
page.click("button[type=submit]")
report = audit(page, levels=["A", "AA"])
for finding in report.violations:
print(finding.sc, finding.target, finding.message)
assert not report.violations, report.to_json()
audit() returns a Report; every Finding carries sc, level, outcome,
impact, target, message, help_url and a detail dict holding the raw
measurements — pixel counts, contrast ratios, coverage fractions, dimensions —
so a human can check the tool's arithmetic.
GitHub Actions
- uses: actions/setup-python@v5
with: { python-version: '3.12' }
- run: pip install sightline-a11y && python -m playwright install --with-deps chromium
- run: sightline ${{ env.PREVIEW_URL }} --level AA --html a11y.html
- uses: actions/upload-artifact@v4
if: always()
with: { name: accessibility-report, path: a11y.html }
Alternatives
| Sightline | axe-core / Lighthouse / Pa11y | Manual audit | |
|---|---|---|---|
| Focus obscuring (2.4.11/2.4.12) | measured | not covered | yes, slowly |
| Focus appearance (2.4.13) | measured from pixels | not covered | yes, subjective |
| Dragging (2.5.7) | detected, flagged for review | not covered | yes |
| Target size (2.5.8) | full exception logic | one rule, needs review | yes |
| Consistent help (3.2.6) | compared across pages | not covered | yes |
| Accessible auth (3.3.8) | paste actively probed | not covered | yes |
| Alt text, ARIA, contrast, headings | not covered — use axe-core | excellent | yes |
| Runs in CI | yes | yes | no |
| Cost per run | free | free | hours of a specialist |
The honest summary: axe-core owns the DOM layer, Sightline owns the geometry-and-behaviour layer, and neither replaces a human for the criteria that need judgement.
Honest limitations
Automated testing cannot prove conformance, and a tool that pretends otherwise is worse than no tool. Sightline is explicit about where it stops:
- 3.3.7 Redundant Entry is not implemented. It requires modelling a multi-step process and knowing what the user already supplied. Any automated verdict would be a guess, so there isn't one.
- Heuristic checks report
review, neverfail. 2.5.7 can tell you a component is drag-operated; it cannot know whether an equivalent button exists elsewhere on the page. - The 2.5.8 "equivalent control" and "essential" exceptions are not detectable. Failures say so.
- 3.2.6 approximates. Relative order is compared among help mechanisms and their landmarks, not against every piece of page content.
- 2.4.13 reads the rendered result. A focus indicator drawn with a background image, a pseudo-element or an SVG is measured the same way as an outline — which is the point — but an animated indicator can be captured mid-transition. Screenshots are taken with animations disabled to limit this.
- Only what is reachable by Tab is examined. Controls behind a menu you have
to open first need you to drive the page and call
audit(page). - The 3.3.8 paste probe dispatches a real event at credential fields, which
fires the page's own handlers. Use
--no-paste-probeagainst production.
How it works
CLI / Python API
│
Auditor ──── instrument(page) init script, must precede navigation
│
Check registry ──── each check: run(page, ctx) -> [Finding]
│
Primitives: png.py dependency-free PNG decoder (zlib only)
color.py sRGB luminance, WCAG contrast ratio
dom.py hit testing, geometry, listener capture
│
Reporters: text │ json │ standalone HTML
The only runtime dependency is Playwright. The PNG decoder is hand-written rather than pulling in Pillow for one narrow job; there are no network calls, no telemetry and no model inference anywhere in the audit.
Development
pip install -e ".[dev]"
python -m playwright install chromium
pytest -q
The test suite runs against two fixtures: tests/fixtures/failing.html plants a
defect for each criterion, and passing.html is the same UI done accessibly.
Every check is asserted in both directions — a checker that fires on everything
is as useless as one that fires on nothing.
Roadmap
- Async Playwright API alongside the sync one
-
pytestplugin:assert_wcag22(page) - Reusable GitHub Action
- SARIF output for code scanning
- Baseline files, so existing debt can be accepted and only regressions fail
- Firefox and WebKit (the checks are engine-agnostic; only capture is tuned)
- EN 301 549 clause mapping in the HTML report
Contributing
New checks, better exception logic and real-world false-positive reports are all welcome — a false positive is a bug here, not a rounding error. See CONTRIBUTING.md.
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file sightline_a11y-0.1.0.tar.gz.
File metadata
- Download URL: sightline_a11y-0.1.0.tar.gz
- Upload date:
- Size: 50.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.25
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ab9a49801398fe17d78e83f176057688c74e2edf86c01817b7f4d4bd2dfa71ea
|
|
| MD5 |
5e326b061224ebbbb312c6143524ab09
|
|
| BLAKE2b-256 |
fb355e3e3f6fc685d5a7b780bbe6fe9891f3a4761b20b3133e05f89e563942a9
|
File details
Details for the file sightline_a11y-0.1.0-py3-none-any.whl.
File metadata
- Download URL: sightline_a11y-0.1.0-py3-none-any.whl
- Upload date:
- Size: 45.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.25
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
843a269960481c567d63dbdbe073b67ec978ea01b0be81b7bf81edc2891bbb99
|
|
| MD5 |
2bcfb1b1c1da9b3e3a1f1a7b30e4484b
|
|
| BLAKE2b-256 |
61b567ed124994d9d917c119a2942542ee65b08c7cdae542910cb38d23149a7f
|