StreamingText
Token-by-token text rendering for AI responses. Handles the full lifecycle: pending, streaming, done, interrupted, error, and ratelimit.
StreamingText — Live Demo
Pick a content type and click "Start streaming"
StreamingText — All 6 States
Import
tsx
import { StreamingText } from "@arc-lo/ui";Basic usage
tsx
<StreamingText.Root stream={readableStream} speed={30}>
<StreamingText.Content />
<StreamingText.Cursor />
</StreamingText.Root>With all parts
tsx
<StreamingText.Root
stream={stream}
speed={30}
chunkSize="char"
onDone={(text) => console.log("Done:", text)}
onError={(err) => console.error(err)}
onInterrupt={(partial) => console.log("Stopped at:", partial)}
>
<StreamingText.Skeleton lines={3} />
<StreamingText.Content />
<StreamingText.Cursor char="▋" />
<StreamingText.Stop>Stop generating</StreamingText.Stop>
<StreamingText.ErrorFallback
message="Something went wrong."
onRetry={() => refetch()}
/>
<StreamingText.RateLimit
message="Rate limit reached."
retryAfter={30}
/>
<StreamingText.Toolbar>
{/* FeedbackBar or custom buttons */}
</StreamingText.Toolbar>
</StreamingText.Root>With plain text (no stream)
tsx
<StreamingText.Root text="Hello, this will animate in." speed={25}>
<StreamingText.Content />
<StreamingText.Cursor />
</StreamingText.Root>Custom rendering
tsx
<StreamingText.Content
render={(text) => <ReactMarkdown>{text}</ReactMarkdown>}
/>Using the hook directly
tsx
import { useStreamingText } from "@arc-lo/ui";
function CustomStreaming({ stream }) {
const { displayedText, state, interrupt, skipAnimation } =
useStreamingText({ stream, speed: 30 });
return (
<div>
<p>{displayedText}</p>
{state === "streaming" && (
<button onClick={interrupt}>Stop</button>
)}
<button onClick={skipAnimation}>Skip animation</button>
</div>
);
}States
| State | Description | Visible parts |
|---|---|---|
pending | Waiting for first token | Skeleton, Cursor |
streaming | Tokens arriving | Content, Cursor, Stop |
done | Stream complete | Content, Toolbar |
interrupted | User stopped generation | Content, Toolbar |
error | Network or model failure | ErrorFallback |
ratelimit | Rate limit hit | RateLimit |
Props
Root
| Prop | Type | Default | Description |
|---|---|---|---|
stream | ReadableStream<string> | — | Stream of text chunks |
text | string | — | Plain text (alternative to stream) |
speed | number | 30 | Delay in ms between chunks |
chunkSize | "char" | "word" | "line" | "char" | How to split text for animation |
onDone | (text: string) => void | — | Called when streaming completes |
onError | (error: Error) => void | — | Called on error |
onInterrupt | (partialText: string) => void | — | Called when user interrupts |