04 / 37 · Reasoning
Streaming text
Tokens arrive softly: the newest words land in blue and settle into ink.
Installation
npx shadcn@latest add "@assistant-ui/elements-streaming-text"
Usage
import { StreamingText } from "@/components/elements/streaming-text";
<StreamingText
segments={[{ text: "Hello world" }, { text: "thread.tsx", mono: true }]}
count={3}
streaming
/>Props
segments*Segment[]Text chunks to stream, optionally marked mono for inline code chips.
count*numberHow many words from the flattened segments are visible.
streaming*booleanWhen true the newest words tint blue and a caret blinks at the end.
classNamestringExtra classes merged onto the root.
Source
streaming-text.tsx"use client";
import { useMemo } from "react";
import { cn } from "@/lib/utils";
export interface Segment {
text: string;
mono?: boolean;
}
export function StreamingText({
segments,
count,
streaming,
className,
}: {
segments: Segment[];
count: number;
streaming: boolean;
className?: string;
}) {
const words = useMemo(
() =>
segments.flatMap((segment) =>
segment.text
.split(" ")
.map((word) => ({ word, mono: segment.mono ?? false })),
),
[segments],
);
return (
<p
className={cn(
"min-h-[8.5rem] max-w-sm text-sm leading-relaxed text-pretty",
className,
)}
>
{words.slice(0, count).map(({ word, mono: isMono }, i) => {
const fresh = streaming && count - 1 - i < 2;
return (
<span
key={i}
className="fade-in animate-in fill-mode-both duration-500 motion-reduce:animate-none"
>
<span
className={cn(
"transition-colors duration-700 motion-reduce:transition-none",
fresh && "text-blue-500 dark:text-blue-400",
isMono &&
"bg-foreground/[0.06] rounded-md px-1.5 py-0.5 font-mono text-[0.85em]",
)}
>
{word}
</span>{" "}
</span>
);
})}
{streaming && count > 0 && (
<span
aria-hidden
className="-mb-0.5 ml-0.5 inline-block h-4 w-0.5 animate-pulse rounded-full bg-blue-500 dark:bg-blue-400"
/>
)}
</p>
);
}