Skip to content

Conversation

Chat Thread

The conversation itself — user and assistant turns, grouping, date dividers, unread marker and a jump-to-latest pill.

2 variantsMIT

Preview

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.

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.json

Usage

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

PropTypeDefaultDescription
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.
agentNamestring"Assistant"Header name for assistant turns.
userNamestring"You"Header name for user turns.
renderAvatar(message: ThreadMessage) => ReactNode—Custom avatar; defaults to a sparkle for the agent and an initial for people.
heightnumber | string420 (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.
barebooleanfalseDrop 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.
footerReactNode—Rendered after the last message inside the scroll area, e.g. follow-up chips.
bottomInsetnumber0Pixels kept free at the bottom for a composer that floats over the thread; Jump to latest sits above it.
contentClassNamestring—Classes for the message column, e.g. `mx-auto max-w-3xl`.
classNamestring—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
};