Putting a11y in the pipeline
Audits are projects. Process is what keeps the audit from being needed again next year.
You can ship a perfect audit and watch the score slide back over six months. The thing that holds the line isn't a person — it's process. Small, repeatable steps in design, review, and CI that catch new bugs before they ship and keep the team's eye on a11y without making it a chore.
This lesson is about turning the audit into a system.
Design checklists
The cheapest a11y bug is one a designer catches in Figma, not one a tester catches in QA. A short checklist designers can run themselves goes a long way:
- [ ] Color: every text/background pair hits 4.5:1 (3:1 for large text and UI elements)
- [ ] Don't rely on color alone — pair it with an icon, label, or shape
- [ ] Tap targets are at least 24×24 CSS pixels (44×44 on touch is better)
- [ ] Heading order is sensible (the H1 is the page subject; H2s are sections)
- [ ] Every interactive element has a visible focus state in the design
- [ ] Form fields have visible labels — no placeholder-only patterns
- [ ] Error states are described in text, not just by color
- [ ] Motion can be reduced (or important motion is short, no auto-loops)
Some teams put this in the Figma file as a final-review template. Some run it in design critique. Either works — what matters is that designers own it, not engineers.
Add an "alt-text" field to your image components in Figma. The conversation about what an image is for belongs in design, not in the engineer's head three weeks later.
Code-review prompts
Reviewers can't audit a whole app, but they can ask three questions on every PR:
- Roles and names. Did anything new show up that's not a real semantic element? If so, does it have the right role and an accessible name?
- Keyboard. Can I tab to the new thing? Can I activate it with the keyboard? Does focus go somewhere sensible after the action?
- State. Are open/closed, pressed/unpressed, expanded/collapsed states exposed via the right ARIA attribute?
A small saved comment template is enough:
A11y check:
- [ ] Tabbed to it; focus is visible
- [ ] Activated with Enter / Space
- [ ] Screen reader announces role + name + state
Three checkboxes. Every PR. The cost is two minutes; the savings show up in the audit you don't have to redo next year.
Definition of done
If your team has a "definition of done" — and they should — accessibility belongs in it. A reasonable addition:
- Component or feature has at least one Testing Library test querying by role
- jest-axe has been run with no violations
- Keyboard-only walk-through has been completed
- Designer has signed off the focus state and contrast
- New strings have been written (or coordinated with content) so labels and errors read naturally to assistive tech
That's not a bar that slows the team down once it's habitual; it's a bar that catches the new bugs at write time.
Don't add definition-of-done items you don't actually enforce. If "manual screen-reader pass" becomes a checkbox everyone clicks without doing, it makes the rest of the list mean less. Start small and grow.
What lives in CI
A practical baseline for most teams:
| Layer | Tool | What it catches | When it runs |
|---|---|---|---|
| Lint | eslint-plugin-jsx-a11y | Obvious sins (missing alt, click on non-button) | Pre-commit + CI |
| Component | jest-axe + Testing Library role queries | Contract: roles, names, states, label associations | On every test run |
| End-to-end | Playwright + @axe-core/playwright | Page-level: landmarks, contrast, focus visibility | On main branch + nightly |
| Score trend | lighthouse-ci (optional) | Direction over time, exec dashboards | Nightly |
Two things to keep this honest:
- Block PRs on lint and component-level a11y. They're fast and almost never flaky.
- Block deploys, not PRs, on the end-to-end suite. It's slower, and it's better to gate the deploy than to make every PR wait.
Team rituals
Process needs anchors in the calendar. Three lightweight ones:
- Quarterly mini-audit. Two hours, one page, one keyboard pass plus a screen-reader spot check. Keeps the muscle alive.
- Bug-bash with a11y track. When you do release-week bug bashes, dedicate one engineer's seat to keyboard-only and one to screen reader. You'll be amazed how much shows up.
- Show-and-tell. When someone fixes a memorable a11y bug — a focus-trap that finally works, a screen-reader announcement that's now sensible — they show it in the next team demo. Normalises the work.
A subtler ritual: invite users. If you can recruit one or two real keyboard or screen-reader users for a paid 60-minute session per quarter, the feedback is worth more than any audit.
Track a single visible metric — total open a11y issues, or end-to-end axe violations on the home page. Watch it move. A trend is more motivating than a one-off score.
A 30-day starter plan
A sane plan for a team starting from "we don't really do this":
- Week 1. Run axe DevTools and Lighthouse on the five most-trafficked pages. Note the top blockers. Fix invisible focus and missing labels.
- Week 2. Add
eslint-plugin-jsx-a11yto the project. Add jest-axe + atoHaveNoViolationssmoke test to the most-used component (button, form input). - Week 3. Add a single Playwright + axe spec to the home page, blocking serious/critical violations. Add a code-review checklist.
- Week 4. Run a 15-minute keyboard audit with two engineers; log findings; sequence them with the priority matrix from lesson 7. Schedule the next mini-audit for 90 days out.
That's it. Four weeks gets you from "ad hoc" to "we have a process." Everything else is upkeep and depth.