Streaming Text

Text revealed as it arrives, with a caret while it is still coming. Driven by elapsed time rather than a per-character interval, so it never drifts or dumps a backlog after a background tab.

src/components/ui/streaming-text.tsx

About

From the component's own source, where it was written.

Text revealed as it arrives, with a caret while it is still coming.

Reveal is driven by elapsed time rather than a per-character interval. A setInterval per character drifts under load and queues up a backlog when the tab is inactive, so a message that streamed while you were away finishes by dumping a thousand characters at once. Reading the clock each frame makes the reveal correct at any frame rate.

Under prefers-reduced-motion the text appears complete immediately. The animation carries no information the text does not, so there is nothing to lose by skipping it.

The output is a live region: a screen reader should hear the answer arrive, not sit in silence until it finishes.

Install

npx astralyx-ui add streaming-text

Usage

import { StreamingText } from '@/components/ui/streaming-text'

<StreamingText text={answer} speed={240} onDone={scrollToBottom} />

Composer

Change the props and the source below follows.

Props
tsx
<StreamingText
  text={answer}
  speed={240}
  streaming={true}
  caret={true}
/>

Examples

Streaming and complete

API

Generated from the source, so it cannot fall behind the code. One table per exported component, then hooks, functions and types.

StreamingText

Text revealed as it arrives, with a caret while it is still coming.

Extends Omit<ComponentProps<'span'>, 'children'>.

PropTypeDefaultDescription
textstringrequiredThe full string. Restarting the reveal is a matter of changing the key.
speednumber240Characters per second, measured against the clock rather than accumulated per frame.
streamingbooleantrueFalse renders the whole string at once — for replaying a finished message.
caretbooleantrue—
onDone() => void—Fires once when the reveal completes. Deliberately not a dependency of the animation, so an inline arrow cannot restart it.

Behaviour

TopicDetailDescription
reduced motionautomaticAppears complete immediately under prefers-reduced-motion — the animation carries no information the text does not.
accessibilityaria-liveA polite live region with aria-busy, so a screen reader hears the answer arrive instead of sitting in silence.