Skip to content
easeweb
Search topics

AI chat replies read out in pieces as they stream in

An AI assistant writes its reply into a role="log" a few words at a time, so a screen reader announces each piece as it lands, never the answer as a whole.

WCAG 2.2
SC 4.1.3AA· new in 2.1
Required by
EN 301 549 (EU)
Broken reply.append(chunk);
The accessibility problem illustrated: No vision:Why is the answer in bits?
01

The problem

Screen reader users hear an AI assistant's answer as a run of broken phrases while it streams, never as one answer they can follow.

02

Try it

Send the question, and listen to how the answer is read as it streams in.

The panel under the page lists each message a screen reader announces, in order. Count them: is the answer ever heard as one piece? Then apply each fix and send again.

Broken Can you fix this?

BrokenA live log that each chunk is added to

Fernhill help

Ask our assistant

Assistant: Hi! Ask me about orders, sizes or returns.

Screen reader says:

(Send the question)

A recreated demo. The broken version is intentionally inaccessible.

03

The fix

Read the reply once, when it is complete.

Before
BrokenA live log that each chunk is added to
<section class="assistant" aria-labelledby="assistant-title">  <h2 id="assistant-title">Ask our assistant</h2>  <div class="messages" role="log" aria-label="Conversation">    <p class="message from-assistant"><span class="visually-hidden">Assistant: </span>Hi! Ask me about orders, sizes or returns.</p>  </div>  <form class="ask-form">    <label for="ask-input">Your question</label>    <input id="ask-input" autocomplete="off">    <button>Send</button>  </form></section>
What is wrong2 points

The conversation is a log with a name, every message says who wrote it, and focus stays in the field. The stream is where it fails.

  • Each chunk is appended to the reply inside the live log, so each one is announced as a message of its own: "Yes, within", then "30 days", then "even if you".
  • Nothing is announced when the reply is complete, so people can't tell from what they heard whether the answer has ended or is still coming.
After

The finished reply, read once

<section class="assistant" aria-labelledby="assistant-title">  <h2 id="assistant-title">Ask our assistant</h2>  <div class="messages" role="log" aria-label="Conversation" aria-live="off">    <p class="message from-assistant"><span class="visually-hidden">Assistant: </span>Hi! Ask me about orders, sizes or returns.</p>  </div>  <p id="reply-status" class="visually-hidden" role="status"></p>  <form class="ask-form">    <label for="ask-input">Your question</label>    <input id="ask-input" autocomplete="off">    <button>Send</button>  </form></section>
How it is fixed4 steps
  1. Add aria-live="off" to the conversation. It stays a named log that people can find and read back through, but what is added to it is not announced while focus is elsewhere, so the stream is silent.
  2. Add an empty, visually hidden p with role="status" beside the conversation, outside it. It is in the page before the first question, so writing into it is a change that is announced, politely.
  3. When the last chunk has arrived, write the whole reply into it. The reply's own text starts with the hidden "Assistant: ", so the screen reader reads "Assistant: Yes, within 30 days, even if you have used it…" once, in one piece, when it finishes what it was saying.
  4. Clear the region when a question is sent, so a reply that happens to match the last one word for word is still a change, and is still read.

This fixes the Before and passes 4.1.3: the reply is presented once, as a whole message, without moving focus. The longer the reply, the longer the wait before anything is heard, and the more there is to listen to in one go.

Easily confused
04

Verify the fix

4 checks, no mouse.

Test with: Screen reader, Keyboard

0 of 4 checked

Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after the chat component, the streaming code or the component library changes.

How screen readers handle role="status", role="log" and role="log" aria-live="off"

Limits

How a screen reader paces a run of live region changes, and whether it reads, queues or skips them, differs between screen readers, browsers and releases, so test the stream with the ones your users use. Auditors may record a reply read in pieces under 4.1.3, since what is presented is not the message, or as a usability finding, since the log does expose each change; either way, people can't follow the answer. This guide covers one assistant on one page; an assistant drawn in a vendor's frame can only be changed through the vendor's settings, and the questions to ask them are in chat widgets that never announce their messages. Scores, tickers and timers that change by themselves are a different case, covered in countdown timers that screen readers keep reading out.

This is a recreated teaching example, not a finding about a named client. The code is a starting pattern: test it in your own product. No completed assistive-technology test is claimed here.

EaseWeb service

Fixed it? Keep it from coming back.

I turn guides like this one into rules your coding agents follow and checks your CI runs. Then I retest by hand with a keyboard and screen readers, periodically.

See the service