We failed a public-sector accessibility audit in 2019. Badly.
The engagement had gone well by every other measure — on time, on budget, a client who liked us. Then an independent audit found 31 issues, four of them blocking, and the launch slipped by eleven weeks while we fixed them at our own cost.
That audit is the reason we now have an accessibility practice. It is also where we learned that the automated tooling we had been relying on had told us we were fine.
What the tools catch
Automated checkers are genuinely useful and they catch roughly a third of real issues. Missing alternative text, insufficient contrast, absent form labels, invalid ARIA. All real, all worth fixing, all cheap to fix.
They caught eleven of our thirty-one. Every one of the four blocking issues was invisible to them, because none of them was a missing attribute. They were structural.
The four that mattered
A modal that did not trap focus. Every attribute was correct. role="dialog", aria-modal, a label, a close button with an accessible name. But Tab moved focus behind the overlay, into a page the user could no longer see. A screen reader user could get into the dialog and then be lost in a document that was visually obscured. No checker flags this, because nothing is missing — the behaviour is simply wrong.
A data table that was a grid of divs. It looked identical. It passed contrast. It had role="table" and role="row" diligently applied. But the roles did not match the DOM nesting, so screen readers announced row and column positions that did not correspond to anything. Worse than no roles at all, because it was confidently incorrect.
Live-updating content with no announcement. A filter panel updated results instantly, which is excellent for sighted users and completely silent for everyone else. No error, no missing attribute — just an interface where a third of users had no idea anything had happened.
A multi-step form that reset focus to the top. After each step, focus returned to the document start. A keyboard user had to Tab through the entire navigation on every step. Technically operable, practically unusable, and impossible to detect without actually doing it.
Each of these needed a decision at the point the component was designed. None could be fixed with an attribute.
What changed in how we work
Accessibility is specified, not reviewed. Design hand-off includes focus order, announcement behaviour and keyboard interaction for every component. It is part of the spec alongside the visual states, not a checklist applied afterwards.
Manual testing is in the definition of done. Every interactive component is operated with a keyboard only, and with a screen reader, before it can be marked complete. This takes about ten minutes per component and finds more than any tool.
We test with the actual combinations. NVDA with Firefox, VoiceOver with Safari, JAWS with Chrome. They disagree in significant ways, and testing one tells you very little about the others.
Automated checks are a floor, in CI. They catch regressions in the cheap third. They are never the evidence that something is accessible.
The commercial argument
Accessibility work is often sold on legal risk, which is true but tends to produce the minimum defensible effort.
The better argument is that these are structural quality problems that happen to affect disabled users first. A modal that does not manage focus is a modal with a broken focus model, and it will produce subtle bugs for everyone. A form that resets focus is a form that has lost track of its own state.
Since 2019 we have passed every accessibility audit at first attempt. The change was not more diligence at the end. It was moving the decisions to where they were cheap — before the component existed.