Guardrails, Not Gatekeepers: Putting Accessibility Checks in the Build
Photo: developer keyboard navigation accessibility testing laptop screen reader, via img.freepik.com
Accessibility work has a familiar shape in most organisations. An audit lands three weeks before launch, a specialist hands over four hundred findings in a spreadsheet, and a team that has never discussed focus order in its life now has to learn the word “contrast ratio” under deadline pressure. Everyone ends up mildly resentful, and the fixes get bolted on rather than built in.
There's a better arrangement, and it isn't exotic. Treat accessibility the way you treat type checking or linting: something the pipeline has an opinion about, every single day.
What automation can and cannot see
Be clear-eyed about this before you start, because disappointment is the main reason these checks get switched off again.
Tools built on axe-core — and most things are — catch the mechanical failures reliably: images with no alternative text, form inputs with no accessible name, duplicate IDs, malformed ARIA, insufficient contrast, skipped heading levels, missing document language. Call it a third of the problem, sometimes less.
They cannot tell you whether the alt text says anything useful. They can't tell you if the tab order follows the visual order, or whether the cookie banner traps focus on mobile Safari, or whether your validation message makes sense when it's read aloud in isolation. Those need a person.
So the split is straightforward: automate everything mechanical, review everything judgemental, and don't pretend one substitutes for the other.
Start from a baseline, not a cliff
If you switch on a strict gate against a ten-year-old codebase, you'll get nine hundred violations on the first pull request. Everyone looks at it, someone mutters, and by Friday it's commented out with the immortal words “todo: re-enable later”.
Baseline instead. Record what's failing today, fail the build only on regressions, and let the existing debt sit there visibly until someone picks it up deliberately. Most tooling supports this in some form: a snapshot file, a budget, a threshold, an ignore list with a comment on each entry explaining why.
A reasonable starter kit for a typical front-end stack:
- jest-axe for component-level rendering, so a broken input gets caught before it reaches a page.
- @axe-core/playwright or cypress-axe for end-to-end journeys, where real routing and real state live.
- pa11y-ci for crawling a list of URLs, which is handy for a marketing site or a set of static pages.
- Lighthouse CI only if you want the performance and best-practice assertions alongside it; as a pure accessibility gate it's blunter than axe.
A minimal pipeline
Something like this, in whatever CI you happen to use:
name: accessibility
on: [pull_request]
jobs:
axe:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci
- run: npx playwright install --with-deps chromium
- run: npm run build
- run: npm run test:a11y -- --baseline a11y-baseline.json
Two details matter more than the tool choice. First, fail the job, don't just print a warning — a warning in a log is a message in a bottle. Second, make the failure land where the developer already is.
Report failures without drowning people
A single grouped comment on the pull request is the sweet spot: one entry per rule, each with the offending selector, a link to the file and line, and a link to the relevant WCAG technique so the fix isn't a research project.
Then do a bit of noise control. Suppress rules the team has consciously decided not to enforce yet, and write down why in the config. Skip pages that are genuinely out of scope rather than letting them fail forever. And don't fail on axe's “incomplete” or “needs review” results — they're a prompt for a human, not a verdict, and treating them as errors is how you teach a team to ignore the tool.
The gates a machine can't run
Add a short manual checklist to the pull request template, and only ask for it when the diff touches UI. Four or five items, no more:
- Tab through the changed journey with a keyboard only. Can you reach everything, see where focus is, and get out of any overlay?
- Spot-check with a screen reader — NVDA with Firefox on Windows, VoiceOver with Safari on macOS. Both free, both good enough for a sanity check.
- Zoom to two hundred percent, then four hundred. Does the layout reflow rather than scroll sideways?
- Check error messages and link text make sense read cold, with no surrounding context.
- If motion is involved, confirm reduced-motion preferences are respected.
That's a ten-minute exercise on a normal change and it catches the class of problem that no scanner will ever flag.
The UK context is a useful lever
For public sector teams, the legal frame is already there. The Public Sector Bodies Accessibility Regulations 2018 require WCAG 2.2 AA conformance, a published accessibility statement, and an annual review — which means an audit trail is not optional paperwork, it's a deliverable. EN 301 549 turns up in procurement questionnaires, and private contracts increasingly mirror it. Even without a legal driver, the Equality Act framing of reasonable adjustments is a decent north star.
Build history helps here. If a regulator or a procurement team ever asks how you assure accessibility, “every pull request has been gated since March 2026, here's the trend” is a much stronger answer than “we had an audit once”.
Track the trend, not the score
Don't chase a perfect hundred. Track violation counts per service over time the way you'd track test coverage: slowly down, never spiking. A dashboard nobody reads is fine if it's the same dashboard that goes red on a bad merge.
A clean build is meant to mean something. Increasingly, that something should include “we didn't ship a barrier today”.