Conversation
Chat Thread
The conversation itself — user and assistant turns, grouping, date dividers, unread marker and a jump-to-latest pill.
2 variantsMIT
Preview
Yesterday
You
Which onboarding emails still go out in English only?
Claude
Four of the seven: welcome, verify-email, trial-ending and first-invoice. The other three already use the locale from the user profile.
Today
You
Translate those four into German.
Keep product names in English, and use the glossary for plan names.
Installation
Copies the component into components/ui and installs its dependencies and theme tokens.
pnpm dlx shadcn@latest add https://tuffui.com/r/chat-thread.jsonUsage
Pass your own data and wire the callbacks to your agent runtime.
import { ChatThread } from "@/components/ui/chat-thread";
import { StreamingMessage } from "@/components/ui/streaming-message";
export function Conversation({ messages, lastReadId }: Props) {
return (
<ChatThread
agentName="Claude"
data={{
unreadFrom: lastReadId,
messages: messages.map((m) => ({
id: m.id,
role: m.role,
at: m.createdAt,
streaming: m.status === "streaming",
// Any node: plain text, a StreamingMessage, tool calls, charts…
content: m.role === "assistant" ? <StreamingMessage data={m} /> : m.text,
})),
}}
/>
);
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
| data* | { messages: ThreadMessage[]; unreadFrom?: string } | — | Turns in order. Consecutive turns from one side within five minutes share a header; `unreadFrom` puts an Unread divider before that message. |
| density | "default" | "dense" | "default" | Dense drops avatars and tightens spacing for side panels. |
| agentName | string | "Assistant" | Header name for assistant turns. |
| userName | string | "You" | Header name for user turns. |
| renderAvatar | (message: ThreadMessage) => ReactNode | — | Custom avatar; defaults to a sparkle for the agent and an initial for people. |
| height | number | string | 420 (100% when bare) | Height of the scroll area. The thread follows new content while you're at the bottom and shows Jump to latest when you scroll up. |
| bare | boolean | false | Drop the shell and panel so the thread is just the scroll area and fills its parent — for full screens where the thread is the page. |
| footer | ReactNode | — | Rendered after the last message inside the scroll area, e.g. follow-up chips. |
| bottomInset | number | 0 | Pixels kept free at the bottom for a composer that floats over the thread; Jump to latest sits above it. |
| contentClassName | string | — | Classes for the message column, e.g. `mx-auto max-w-3xl`. |
| className | string | — | Extra classes for the shell. |
type ThreadMessage = {
id: string;
role: "user" | "assistant";
content: ReactNode;
at: string; // ISO timestamp
author?: string;
streaming?: boolean; // shows a caret and keeps the thread pinned while it grows
};