A developer-focused look at turning automated accessibility candidates into verified findings, evidence, remediation ownership, and retestable fixes.
Automated accessibility scanners are useful.
But a scanner warning is not the same thing as a verified accessibility finding.
That sounds obvious, but it has a major impact on how accessibility audits should be designed.
In a recent website accessibility review, we tested 7 representative pages and ended with 29 verified findings.
The interesting part was not the number.
It was everything that happened between automated detection and the final finding.
The workflow looked roughly like this:
Automated discovery
|
v
Candidate normalization
|
v
Manual verification
|
+------> Dismissed / not reproducible
|
+------> Needs additional review
|
v
Verified finding
|
v
Evidence capture
|
v
WCAG mapping
|
v
Remediation ownership
|
v
Priority assignment
|
v
Retest
That middle layer is where much of the real accessibility work happens.
Scanner output should be treated as a queue, not a report
A common accessibility workflow looks like this:
Run scanner -> Export results -> Send PDF
Technically, that produces a report.
But it does not necessarily produce a useful audit.
Automated tools can identify many patterns efficiently:
- missing attributes
- possible color contrast failures
- structural issues
- form-related patterns
- invalid ARIA
- semantic inconsistencies
- certain accessible-name problems
They are very useful for candidate discovery.
But there are many things they cannot fully determine from static analysis.
For example:
- Is keyboard focus visible throughout a real interaction?
- Does focus move logically when a menu opens or closes?
- Is a control understandable in its actual context?
- Does a form still make sense after placeholder text disappears?
- Is an iframe usable through the complete keyboard journey?
- Does responsive reflow make content unusable at a narrow viewport?
- Does an apparently duplicated link become ambiguous to a screen-reader user?
- Is the defect controlled by the website team or by a third-party widget?
Those questions require interaction and context.
So architecturally, I prefer treating scanner results as candidates.
Conceptually:
type FindingState =
| "candidate"
| "verified"
| "dismissed"
| "needs_manual_review";
A candidate should not become a reportable finding simply because a tool emitted it.
Normalize findings before verification
When multiple tools are used, they often report the same underlying problem differently.
Imagine three checks identify something around the same form field:
Tool A:
Input missing label
Tool B:
Form control has no accessible name
Tool C:
Possible WCAG 1.3.1 issue
Those are not necessarily three findings.
They may all describe one underlying implementation problem.
A useful audit pipeline therefore needs normalization.
A simplified conceptual model could look like this:
interface AccessibilityCandidate {
page: string;
component?: string;
selector?: string;
sourceTool?: string;
sourceRule?: string;
category:
| "keyboard"
| "focus"
| "forms"
| "names"
| "structure"
| "contrast"
| "reflow"
| "third_party"
| "other";
observation: string;
state:
| "candidate"
| "verified"
| "dismissed"
| "needs_manual_review";
}
This gives the reviewer something more useful than several unrelated scanner exports.
Verification needs a reproducible test
For each serious candidate, the reviewer should be able to answer:
Can another person reproduce what I observed?
That changes how findings are written.
Instead of:
Button is inaccessible.
A useful finding needs considerably more context.
For example:
Page:
Mobile navigation
Component:
Menu toggle
Test method:
Keyboard interaction and accessible-name inspection
Observation:
The control is visually recognizable as the navigation trigger,
but its programmatic name does not clearly communicate its purpose.
Evidence:
Screenshot + inspected accessibility properties
Remediation direction:
Provide a meaningful accessible name and verify keyboard,
focus, and expanded/collapsed state behavior.
Retest:
Repeat keyboard and accessibility-tree inspection after remediation.
The point is not that every report needs this exact structure.
The point is that the finding should survive handoff.
A developer who was not present during the audit should still understand what was observed.
Evidence should travel with the finding
This becomes especially important when accessibility work involves:
- developers
- agencies
- compliance teams
- external counsel
- third-party vendors
- multiple rounds of remediation
A useful finding therefore needs traceability.
Conceptually:
interface VerifiedAccessibilityFinding {
id: string;
page: string;
component: string;
severity: "high" | "medium" | "low";
observation: string;
userImpact: string;
reproductionSteps: string[];
wcagReferences: string[];
evidence: {
screenshots?: string[];
notes?: string[];
selectors?: string[];
};
remediation: string;
owner:
| "site"
| "shared_component"
| "third_party_vendor"
| "unknown";
retestRequired: boolean;
}
This is not meant as a universal specification.
It illustrates an important principle:
The evidence model should be designed before the final report is generated.
Otherwise screenshots, test notes, selectors, scanner output, and remediation recommendations tend to become disconnected artifacts.
Keyboard testing exposes problems static scanning cannot
Keyboard accessibility is one of the clearest examples.
A DOM scanner can inspect markup.
It cannot fully experience an interaction sequence the way a user does.
A basic manual keyboard pass may include:
1. Load page from a clean state
2. Do not use the mouse
3. Press Tab through interactive elements
4. Observe focus visibility
5. Check focus order
6. Activate buttons and links
7. Open menus/dialogs
8. Verify focus movement
9. Close overlays
10. Verify where focus returns
For complex UI, you also need to consider:
Escape
Enter
Space
Arrow keys
Shift + Tab
depending on the component.
A navigation menu might technically contain focusable links and still create a poor keyboard experience.
A modal may open correctly but fail to manage focus.
A custom dropdown may work perfectly with a mouse while being unusable from the keyboard.
These are behavioral defects, not just markup defects.
Accessible names need context too
Accessible-name problems are another area where raw scanner counts can be misleading.
Consider a page containing several cards:
<a href="/property/1">View More</a>
<a href="/property/2">View More</a>
<a href="/property/3">View More</a>
Visually, each link may appear directly below a different property name.
But depending on implementation and assistive-technology navigation, repeated generic link names can become difficult to distinguish.
The technical question is not merely:
Does the <a> element contain text?
It is:
Does the accessible name communicate the purpose of the link
in the context in which users may encounter it?
That requires review.
Forms require interaction, not just markup inspection
Forms are another major source of accessibility findings.
One pattern we encountered involved fields relying heavily on placeholder text.
Conceptually:
<input
type="text"
placeholder="First Name"
>
The concern is not simply that placeholder text exists.
The concern is when placeholder text effectively becomes the only persistent labeling mechanism.
Once the user begins typing:
"First Name" -> disappears
A stronger pattern generally separates the visible label from optional guidance:
<label for="first-name">First name</label>
<input
id="first-name"
name="first_name"
type="text"
autocomplete="given-name"
>
But even that does not finish the review.
You may still need to verify:
- required-state communication
- validation
- error identification
- error association
- focus behavior
- instructions
- status messages
- keyboard submission
Again, accessibility is behavioral.
Third-party widgets create an ownership problem
One of the most useful lessons from this audit came from embedded third-party property functionality.
Suppose the website architecture looks like:
Main website
|
+-- Header
+-- Content
+-- Lead form
|
+-- iframe
|
+-- Third-party property application
|
+-- Search
+-- Filters
+-- Cards
+-- Dialogs
An accessibility review might detect a problem inside the iframe.
But who owns the fix?
That matters operationally.
The website team may control:
iframe title
surrounding instructions
embed configuration
fallback links
alternative access path
The vendor may control:
internal button names
keyboard behavior
dialog focus
ARIA relationships
internal heading structure
widget rendering
A remediation report that ignores this boundary can create impossible tickets.
For example:
Developer ticket:
Fix keyboard behavior inside vendor iframe.
The developer may have no access to that code.
A better finding identifies remediation ownership:
Issue owner: Third-party vendor
Site-controlled mitigation:
- Review embed configuration
- Improve surrounding context
- Provide alternative path if appropriate
Vendor action:
- Correct keyboard interaction
- Correct accessible names
- Review focus management
That distinction dramatically improves remediation planning.
Shared components should affect prioritization
Severity alone is not enough for remediation planning.
Imagine these two issues:
Issue A
Medium severity
Appears once
Issue B
Medium severity
Exists inside global navigation
Appears on 80 pages
Both may have the same severity label.
They do not have the same remediation leverage.
A useful prioritization model therefore considers at least:
user impact
x journey importance
x component reuse
x frequency
x remediation effort
You do not necessarily need a mathematical score.
But those dimensions should influence planning.
In our review, findings naturally grouped into workstreams such as:
Critical user journeys
|
+-- navigation
+-- forms
+-- primary interactions
Shared components
|
+-- header
+-- footer
+-- cards
+-- reusable buttons
Third-party dependencies
|
+-- embeds
+-- property tools
+-- vendor widgets
Structural / visual issues
|
+-- headings
+-- contrast
+-- alternative text
+-- responsive behavior
This is much easier for an engineering team to consume than 29 unrelated tickets.
Accessibility remediation should behave like a verification loop
Another mistake is considering a ticket finished as soon as the code changes.
Accessibility remediation should work more like:
Observe
|
v
Reproduce
|
v
Fix
|
v
Deploy
|
v
Retest
|
+------ Fail ------> Fix again
|
v
Verified
Why?
Because accessibility fixes can introduce regressions.
For example:
Add ARIA label
|
+--> accessible name improves
|
+--> but duplicate naming appears elsewhere
or:
Modify focus management
|
+--> modal opens correctly
|
+--> but focus no longer returns to trigger
or:
Increase text size / layout flexibility
|
+--> improves readability
|
+--> causes another control to become clipped
A fix is a hypothesis until it has been tested.
What the 7-page audit produced
After candidate discovery, manual review, normalization, and verification, the final review contained 29 verified findings across 7 representative pages.
The useful output was not simply:
29 accessibility problems
It was a structured set of observations with context around:
- affected pages
- components
- keyboard behavior
- focus behavior
- accessible names
- forms
- page structure
- image alternatives
- contrast
- responsive behavior
- third-party functionality
- evidence
- remediation direction
- ownership
- retesting
That creates something an engineering team can actually work from.
The architecture I would recommend for an accessibility audit system
If I were designing the technical system from scratch, I would separate it into six layers.
Layer 1: Collection
Capture:
URLs
DOM state
scanner output
screenshots
viewport data
interaction observations
component context
Layer 2: Candidate normalization
Deduplicate tool output and map similar detections into common categories.
axe candidate
WAVE candidate
manual observation
custom rule
|
v
normalized candidate
Layer 3: Human verification
Every candidate becomes one of:
verified
dismissed
needs more review
not applicable
Layer 4: Evidence
Associate the verified observation with enough information to reproduce it.
finding_id
page
component
steps
screenshot
selector/context
notes
Layer 5: Remediation planning
Add:
severity
ownership
recommended direction
shared component impact
priority
Layer 6: Retesting
The same finding ID should survive remediation.
A11Y-017
Initial:
verified
After remediation:
retest_pending
Retest:
passed
That gives you a real lifecycle instead of a one-time PDF.
The bigger engineering lesson
Accessibility testing is often discussed as a tooling problem.
I think that framing is incomplete.
The harder problem is evidence management and verification.
Tools can produce thousands of observations.
The valuable system is the one that can reliably answer:
What was detected?
What was actually verified?
What evidence supports it?
Where does it occur?
Who controls the remediation?
What should change?
Was the change retested?
That is a much more interesting engineering problem than simply running another scanner.
And it is also where automated accessibility testing and human accessibility review complement each other rather than compete.
Automation gives us scale.
Human verification gives us context.
Evidence gives us traceability.
Remediation ownership gives us an execution path.
Retesting closes the loop.
For accessibility work that needs to survive real engineering handoffs, all five matter.
This article is adapted from a real-world accessibility review covering 7 representative pages and 29 verified findings.
Original case study:
https://www.auditzo.com/case-study/real-estate-website-accessibility-evidence-review
Disclosure: This article was prepared with AI writing assistance and reviewed and edited by Shivam Sharma based on the actual audit workflow and underlying case study.
Top comments (0)