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

StateDescriptionVisible parts
pendingWaiting for first tokenSkeleton, Cursor
streamingTokens arrivingContent, Cursor, Stop
doneStream completeContent, Toolbar
interruptedUser stopped generationContent, Toolbar
errorNetwork or model failureErrorFallback
ratelimitRate limit hitRateLimit

Props

Root
PropTypeDefaultDescription
streamReadableStream<string>Stream of text chunks
textstringPlain text (alternative to stream)
speednumber30Delay in ms between chunks
chunkSize"char" | "word" | "line""char"How to split text for animation
onDone(text: string) => voidCalled when streaming completes
onError(error: Error) => voidCalled on error
onInterrupt(partialText: string) => voidCalled when user interrupts