Problem
Someone asks a question and the model needs thirty seconds to answer it. A spinner held for thirty seconds is indistinguishable from a page that’s crashed. The viewer can’t tell whether the thing is working, stuck, or already failed, and the only available action is to wait or reload.
Solution
Open a long-lived response and emit the answer in pieces as it’s produced. The transport is server-sent events: a single HTTP response with a text/event-stream content type that stays open while the server writes named events into it. Anthropic’s stream is a fair model of the shape—message_start, then a run of content_block_delta events each carrying a text_delta, then message_delta and message_stop. The client appends each delta and re-renders.
The reason this works isn’t that it’s faster. End to end it’s usually a little slower, because chunked encoding and per-event overhead cost something. It works because it moves the wait. Nielsen’s three limits put 0.1 seconds at the threshold of feeling instantaneous, 1 second at the limit of uninterrupted flow, and 10 seconds at the limit of holding attention at all. A thirty-second generation blows through all three. The same generation streamed becomes a one-second wait followed by twenty-nine seconds of reading, and reading isn’t waiting. The number that governs the experience is time to first token.
Three decisions carry most of the design:
- The gap before the first token. This is the only part that is still a wait, and it needs its own treatment—a caret, a shimmer, a “thinking” line. Once deltas arrive the text itself is the progress indicator.
- Incremental markdown. Text streams a character at a time, but a fenced code block, a table, or a link is only meaningful once closed. Rendering markdown on every delta makes tables reflow and half-written links flash as literal brackets. The usual fix is to render closed blocks and hold open ones as plain text until their delimiter arrives.
- Scroll. Pinning the viewport to the bottom is correct until the reader scrolls up to re-read something. Detach on any upward scroll, then offer an explicit return to the live edge.
Screen readers need the opposite of what sighted readers get. An aria-live region fed one token at a time either interrupts itself continuously or queues thousands of announcements. Announce the response once on completion, and expose the arrival of the first token and the end of the stream as discrete cues rather than narrating the middle.
Use when
The output is linear prose a person reads top to bottom, and generation runs longer than about a second. This covers nearly every chat answer.
Don’t use when
The output is only usable whole. A JSON payload, an image, a table that reflows on every row, or a three-word answer all do better arriving complete. Streaming a structure that rearranges itself as it grows is more agitating than a spinner.
Trade-offs
Streaming publishes the first sentence before the model has written the last one, so a confident wrong opening stays on screen and can’t be silently revised. It also makes latency look better than it measures, which quietly rewards teams for optimising the wrong number. Incremental rendering costs real complexity in the markdown layer, and every product that skipped it shipped flickering tables. Auto-scroll is the single most complained-about behaviour in the pattern, and it always comes from someone reading while the answer arrives.
Checklist
- What’s the time to first token, and is it measured separately from total generation time?
- What fills the gap between send and first token?
- Does a half-written code fence, table, or link render as literal markdown while it streams?
- When the reader scrolls up mid-stream, does the view stay where they put it?
- Is there an obvious way back to the live edge once they have detached?
- What does a screen reader hear while three hundred tokens arrive?
- If the connection drops at token 200, what stays on screen and what does the viewer see?
- Can the viewer copy or act on a partial response before it finishes?
- Does the composer stay usable while a response is streaming?
- Is the end of the stream marked in a way a person and a screen reader can both detect?
Compare
ChatGPT keeps the composer live while a response streams, so a correction can be typed before the answer lands. Claude moves long structured output into a side panel rather than the transcript, so a document being generated does not reflow the conversation underneath it. Perplexity resolves and shows its sources before the prose begins, so the citations a sentence points at already exist by the time the sentence arrives. GitHub Copilot goes the other way for inline code completion and presents a suggestion whole, because ghost text that grows character by character inside an editor is unreadable.
Related
Message turn is the container each streamed answer lands in. Stop generation is the control that only exists because streaming made the middle of a response a place the viewer can stand. Reasoning disclosure streams too, on a separate track and usually collapsed. Artifact panel is where output goes when streaming it into the transcript would wreck the transcript. Generation error is what the pattern owes you when the stream dies halfway.