Skip to content
artificia11y Amplifying everyone

Dynamic and streaming output

Streaming output is text that appears piece by piece as the model produces it, rather than arriving all at once. For a sighted user it feels fast and alive. For a screen reader user it is the single most common way AI interfaces break, because the page is changing many times a second.

  • Streaming output updates the page many times a second, which is the most common way AI interfaces break for screen reader users.
  • Use one pre-existing, polite live region rather than inserting a new one when the response begins.
  • Announce in meaningful chunks like sentences, or just the start and end, not every token.
  • Leave focus where the user put it while text streams, and offer a way to jump to the response.
  • Always give a clear, keyboard-operable stop control that is reachable while the text is still streaming.

If you put generated text in a live region and let every token update it, a screen reader tries to announce each change. The result is a barrage that no one can follow, which this site calls announcement flooding. The fix is not to remove the live region but to control what it announces and how often.

Scott O’Hara’s guidance on live regions is the place to start. Put a single live region in the DOM ahead of time and update its contents, rather than inserting a new live region when the response begins, because a region that appears at the same moment as its content is announced unreliably. Use aria-live="polite" so the announcement waits for a pause rather than interrupting. The native output element gives you this behavior with role="status" built in.

Rather than announcing every token, announce in units a person can take in, such as a complete sentence or paragraph, or wait until the response settles and announce that it is ready. A good default is to show the text streaming visually for sighted users while announcing the response in larger, debounced chunks for screen reader users, so both audiences get a version that suits them.

Consider announcing only the start and the end

Section titled “Consider announcing only the start and the end”

For many cases the calmest experience is to announce that the assistant is generating, stay quiet while the text streams, and then announce that the response is ready so the user can read it at their own pace. James Scholes argues that you should design the screen reader experience deliberately rather than delegating it to a role like log, whose live behavior is inconsistent across screen readers. Decide what the user should hear, and build exactly that.

Do not move focus as the text streams, and do not trap it inside the growing response. Leave focus where the user put it, announce that the response is ready, and offer a way to jump to it for users who want to read from the top. This connects to the focus requirements in WCAG Operable.

A streaming response can be long, so a user has to be able to stop it. Give a clear, keyboard-operable stop control that is reachable while the text is still streaming, as covered in WCAG Operable.

Audience: Engineer

The recipe is a single pre-existing aria-live="polite" region, updated with debounced chunks the size of a sentence or paragraph rather than tokens, with focus left alone and a real stop button wired to cancel the stream. Do not reach for role="log" and assume it works, since its behavior varies across screen readers. Test with a real screen reader, not only an inspector, because flooding only shows up when something is actually speaking.

Show the text streaming for sighted users in one element, and announce settled chunks through a separate output element, which is a polite live region by default.

<!-- Sighted users watch this grow token by token -->
<div id="response"></div>
<!-- Screen readers hear settled chunks from this pre-existing region -->
<output id="sr-announce" class="visually-hidden"></output>
<button id="stop" type="button">Stop generating</button>
// Announce whole sentences, not every token
let pending = '';
function onToken(token) {
document.getElementById('response').append(token); // visible stream
pending += token;
const end = pending.lastIndexOf('. ');
if (end !== -1) {
document.getElementById('sr-announce').textContent = pending.slice(0, end + 1);
pending = pending.slice(end + 1);
}
}

Leave focus where the user put it while this runs, and wire the stop button to actually cancel the request.

Audience: Accessibility Specialist

This is a place where automated tools tell you almost nothing, so test with real assistive technology. Listen to a full streamed response with a screen reader and check that it is announced in a way you can follow, that it does not repeat or talk over itself, and that focus does not jump. Try a long response and a stopped one, since both stress the live region differently.

Audience: Self-advocate

Streaming is the feature that most often turns a useful tool into an unusable one with a screen reader. Either nothing is announced and you do not know an answer arrived, or every fragment is announced and it is a wall of noise. What works is being told the assistant is generating, then being told the answer is ready, and being able to read it yourself without your focus being dragged around.

Audience: Designer

Design a clear done state, not only the streaming animation. Sighted users get progress from the motion, but everyone needs an unambiguous signal that the response has finished, expressed as text and not just the absence of a spinner. Pair that with a visible, persistent stop control during generation.