Streaming text Pro

Model output, paced. Tokens arrive in bursts and stalls, and the text doesn't show it: it reveals at a steady rhythm, speeding up only to drain a backlog, and each new piece surfaces out of a soft blur instead of popping in. A caret breathes at the end while more is coming.

Why do springs feel better than easing curves?

Pro preview

Installation

Terminalbash
npx @neelshha/ui@latest login
npx @neelshha/ui@latest add streaming-text

Usage

page.tsxtsx
import { StreamingText } from "@/components/ui/streaming-text";

<StreamingText streaming={isStreaming}>{text}</StreamingText>

Pass the whole text received so far, not each chunk. If a new value doesn't continue the last one, like a regenerated answer, it starts over from the part the two share. Text that's there on mount, like an answer from history, shows at once.

With the AI SDK

page.tsxtsx
const { messages, status } = useChat();

messages.map((message, i) => (
  <StreamingText
    key={message.id}
    streaming={status === "streaming" && i === messages.length - 1}
  >
    {message.parts
      .map((part) => (part.type === "text" ? part.text : ""))
      .join("")}
  </StreamingText>
));

Source

API

Other props go to the div. It sets aria-busy while text is still coming, and wraps white space like pre-wrap. With reduced motion, text shows as it arrives, without the fade.

PropTypeDefaultDescription
childrenstringEverything received so far. Append to it as chunks arrive.
streamingbooleanfalseTrue while more text is on its way. Keeps the caret on.
speednumber90The steady pace, in characters per second. A backlog drains faster, so the text never trails far behind what has arrived.
onDone() => voidFires once a finished stream has been revealed in full.