Blog · · 11 min read
AI agent accessibility for streaming, approvals, and recovery

AI agent accessibility can fail even when the surrounding page passes an audit. A screen reader may miss tool-mode state, announce every streamed token, or lose the result after an approval dialog closes. Keyboard users may reach controls hidden with an off-screen chat panel. Labels on the prompt box cannot cover these failures. The product needs one accessibility contract over the durable agent run, from queued work through progress, input requests, approval, cancellation, failure, and recovery. This guide maps those states to programmatic status, keyboard actions, focus behavior, timing rules, and an assistive-technology test matrix.
Build the accessibility contract over the run
The chat transcript is one view of a product workflow. The same durable run record that drives progress, approval, and recovery should also drive accessibility state. The interface can then restore a stable answer after a refresh, reconnect, or handoff.
Build it in this order:
- Define a small run-state machine and the commands permitted in each state.
- Assign every state a visible label, programmatic status, focus policy, and keyboard-reachable action set.
- Separate visual token streaming from assistive announcements.
- Represent tool modes and expanded panels with native control semantics and explicit state.
- Bind each approval dialog to one durable request and a documented timing policy.
- Keep failure, cancellation, and recovery states available after the transient chat update ends.
- Test real state transitions with keyboards, screen readers, zoom, contrast modes, and reduced motion.
WCAG 2.2 is the normative baseline. The contract is the product architecture that applies that baseline to agent behavior. A widget checklist covers message input and output. The run contract must also cover every state of action-taking work.
Why page-level checks miss agent failures
Agent interfaces create several timing and state problems that ordinary forms do not:
- The visible answer changes token by token, but a screen reader needs useful semantic updates.
- A tool or mode can become active without a new page or obvious text change.
- Work may continue after the browser disconnects.
- The next required action can move from the prompt to a progress card, approval dialog, or recovery panel.
- A timeout can expire while someone is reading the proposed effect.
- Cancellation may be requested before the backend knows whether an in-flight effect committed.
W3C's guidance for status messages explains that important content changes must be programmatically available without taking focus. That applies directly to queued, working, blocked, completed, and failed run states. It does not mean every token belongs in a live region.
Two issue reports show how narrow UI choices create larger failures. An Open WebUI screen-reader report describes tool controls that exposed neither their purpose nor selected state. A separate Open WebUI repair record covers nested controls, popup state, hidden focusable content, skip targets, and keyboard navigation. These are author and maintainer reports, not prevalence statistics. They provide concrete failure cases for regression tests.
Map every durable state to accessible behavior
Draft the state table before changing components. The exact state names can differ, but each row needs one durable meaning.
| Run state | Programmatic status | Focus and keyboard rule | Available action |
|---|---|---|---|
queued | Announce that work was accepted and why it is waiting | Keep focus on the initiating surface | Cancel when policy allows |
working | Announce phase changes, not every token or event | Do not move focus for background progress | Pause or cancel when supported |
input_required | Identify the missing field and affected step | Move focus only after a user action opens the request | Supply input or hand off |
approval_required | Name the proposed effect, target, and expiry | Put focus in the opened dialog and contain it there | Approve, reject, or modify |
completed | Announce the verified outcome once | Keep or return focus to a stable run summary | Inspect receipt or start another task |
failed | Announce the failed step, known effects, and owner | Focus the error summary only when the user's action caused the transition | Retry safe work, recover, or hand off |
cancelled | Distinguish stopped future work from committed effects | Return focus to the run summary or trigger | Inspect effects or compensate |
recovered | Announce what changed and what remains unresolved | Preserve a stable path to the original failure | Close or continue under a new action version |
Do not derive this table from model prose. Project it from product and orchestration events. The visible sentence can change, but the underlying state and available commands must remain stable.
Store the accessibility behavior alongside the run view instead of scattering it across components:
type RunA11yState = {
runId: string;
status:
| "queued"
| "working"
| "input_required"
| "approval_required"
| "completed"
| "failed"
| "cancelled"
| "recovered";
statusSummary: string;
announcementKey: string;
announcementPriority: "polite" | "assertive" | "none";
focusTargetId?: string;
availableCommands: Array<"pause" | "cancel" | "approve" | "reject" | "modify" | "retry" | "handoff">;
actionVersion: number;
updatedAt: string;
};
announcementKey should identify a semantic transition such as run-42:working:validation-complete, not a rendered token count. The client can suppress duplicate keys after reconnecting while still rendering the current status.
Implement AI agent accessibility for streaming output
Use separate channels for sighted users and assistive technology:
- A visual stream can update frequently for sighted users who choose to watch generation.
- An assistive summary channel announces meaningful boundaries, such as a completed sentence, a named phase change, a request for input, or a terminal result.
A current streaming and screen-reader implementation article discusses throttling, sentence-level announcements, and testing with assistive technology. Those are implementation options to test against your own content and users. One batching interval will not work for every screen reader or workflow.
For the stream:
- Do not wrap the entire token stream in an assertive live region.
- Do not replace the live-region node on every update. Keep one stable announcer and update its text at semantic boundaries.
- Let users pause visual streaming or choose a reduced-update presentation when your interface supports continuous motion.
- Keep the final response in ordinary document structure so it remains navigable after streaming ends.
- Announce product state separately from generated content. "Waiting for approval" is not part of the model answer.
- Cancel queued announcements when a newer state makes them false.
The chatbot accessibility guide from Make Things Accessible covers focus, labels, announcements, rich media, contrast, overload, and timeout concerns. An agent interface needs those foundations plus durable task state and action controls.
Make tool modes and hidden panels operable
Start with native buttons, inputs, dialogs, lists, and headings. An icon-only tool toggle needs an accessible name and a programmatic selected or pressed state. If it opens a popup, expose whether the popup is open and connect the control to the owned content.
Do not hide a panel by moving it off-screen while leaving its descendants in the tab order. A CopilotKit repair addressed a closed agent chat that remained exposed to keyboard and assistive-technology navigation until the hidden panel was made inert. Turn that bug into a regression case: close the panel, press Tab through the surrounding page, and inspect the accessibility tree.
Do not communicate tool state through color and icon shape alone. For example, a web-search button can expose "Web search, on" through a pressed state while the visible icon remains compact. If selecting a tool changes the data the agent may send, put that consequence in nearby text or a disclosure that keyboard users can reach.
Microsoft's accessibility and inclusion guidance for agents recommends user control, multiple interaction methods, descriptive alternatives, logical focus order, sufficient timing, error recovery, and testing with people with disabilities. Use that guidance as a design baseline, then enforce the behavior in component and end-to-end tests.
Make approval focus and timing explicit
An approval is a workflow state, not a generic confirmation. The accessible name should identify the proposed action. The description should include the target, material parameters, consequences, reversibility, and expiry.
Persist and open approvals in this order:
- Persist the approval request with its action version before opening the dialog.
- Open the dialog in response to a known state transition.
- Move focus to a suitable element inside it, based on the content and risk.
- Keep Tab and Shift+Tab inside while the modal remains active.
- Offer approve, reject, and modify actions as named controls.
- On close, return focus to the invoking control or the stable run summary when the original control no longer exists.
- If the action changed, expire the old request instead of reusing its decision.
The WAI-ARIA modal dialog pattern documents keyboard interaction, accessible naming, focus containment, initial focus choices, and focus return. Copy the documented behavior, rather than adding role="dialog" to an overlay that lacks the required keyboard and focus handling.
Approval expiry also needs an accessibility policy. W3C's Timing Adjustable guidance requires users to turn off, adjust, or extend content-imposed limits unless a documented exception applies. Warn before expiry, make the remaining time available programmatically, and preserve the proposed action if the request expires. An expired approval should return to a recoverable state instead of discarding the work and forcing the user to reconstruct it.
Keep cancellation, errors, and recovery understandable
For a failed run, persist:
- the user's original goal;
- the current run and action version;
- completed, failed, skipped, and unknown effects;
- the control that caused the transition;
- the next safe actions;
- the current recovery owner.
Use assertive announcements sparingly for conditions that require immediate attention, such as a destructive approval becoming invalid while its dialog is open. Routine progress can remain polite. A failure that appears after background work should remain in the durable run summary until someone resolves it.
Separate cancel requested from cancelled. If a side effect is in flight, show that the stop request is pending. Once the backend reconciles the effect, announce whether it committed and expose compensation or handoff when needed. This prevents the accessibility layer from claiming safety before the product has established the outcome.
When recovery opens another modal or route, maintain a return path to the original run. Do not rely on browser history alone. Store the run ID and intended focus target so a reload can reconstruct the same context.
Verify the contract across assistive technology and run state
Automated scans can catch missing names, invalid nesting, contrast defects, and some focus problems. They cannot prove that streaming cadence is usable or that a recovery flow makes sense. Pair automation with manual state-transition tests.
Test these combinations:
- Keyboard only, including reverse tab order and shortcut conflicts.
- At least one screen reader and browser combination supported by your product team.
- 200% and 400% zoom with reflow.
- High-contrast or forced-colors mode.
- Reduced-motion preference.
- Speech input or another non-pointer input method where relevant.
- Slow reading and interaction across approval timeouts.
- Browser disconnect and reload during every nonterminal run state.
Run each combination through these cases:
- Start a run with and without a tool mode enabled.
- Stream a long response while product progress changes in parallel.
- Request missing input after several completed steps.
- Open, modify, reject, and expire an approval.
- Close and reopen the agent panel.
- Request cancellation during an in-flight effect.
- Force partial failure, retry, compensation, and handoff.
- Reload and require the same state, controls, focus destination, and summary.
The assertions should cover the accessible name, role, state, announcement, focus target, and keyboard actions for each transition. Include duplicate-event and reconnect tests so the announcer does not repeat stale progress.
Common implementation mistakes
A single global live region mixes generated text with product status and makes announcement order unpredictable.
Moving focus for every progress update interrupts reading. Announce status without moving focus unless the user opened a new interaction surface or an urgent error requires action.
Adding ARIA to a custom clickable element does not recreate all native button behavior. Use the native control when possible.
Closing a panel visually while leaving it focusable creates a hidden keyboard route. Verify both the tab order and accessibility tree.
An approval timeout with no extension or recovery path can erase work before a user finishes reviewing it.
Running only an automated scanner misses streaming cadence, recovery comprehension, and cross-state focus continuity.
What to do next
Choose one action-taking workflow and add accessibility columns to its durable state table: programmatic status, announcement boundary, focus target, keyboard commands, timing rule, and recovery path. Then run the eight transition cases above with a keyboard and your supported screen-reader combination. AI agent accessibility is ready to ship only when a reload, expired approval, partial failure, and cancellation preserve the same understandable run state as the normal path.
References
- Web Content Accessibility Guidelines 2.2 provides the normative accessibility requirements used as the baseline for the run contract.
- W3C: Understanding Status Messages supports programmatic status updates that do not take focus.
- W3C: Understanding Timing Adjustable supports user control over content-imposed approval and input time limits.
- WAI-ARIA Authoring Practices: Dialog Modal Pattern supports the keyboard, naming, focus-containment, and focus-return behavior for approvals.
- Microsoft: Accessibility and inclusion for agent design supports agent-specific guidance for control, input methods, descriptions, focus, timing, recovery, and inclusive testing.
- Make Things Accessible: Chatbots and Web Accessibility supplies adjacent implementation coverage for keyboard operation, focus, labels, announcements, contrast, cognitive load, and timeouts.
- Tian Pan: The Accessibility Gap in AI Interfaces Nobody Is Shipping Around supplies a current implementation discussion of streaming cadence, screen-reader announcements, and assistive-technology testing.
- Open WebUI issue #17150 is a direct screen-reader report about tool controls missing purpose and state.
- Open WebUI issue #27698 is a concrete repair record for nested controls, popup state, hidden content, skip targets, and keyboard behavior.
- CopilotKit pull request #6110 is a repair record for a visually closed agent chat remaining keyboard-accessible until its panel was made inert.