Components / Data display
ChatThread
A chat thread with grouped messages, reactions, read receipts, typing, and a composer with attachments.
Install
npx shadcn@latest add https://irisui.dev/r/chat-thread.jsonimport { ChatThread } from "@/components/iris/chat-thread/chat-thread";Preview
ChatThread
A chat or support thread with grouped messages, day separators, reactions, read receipts, a typing indicator and a composer with attachments.
When to use
- Person-to-person messaging, support conversations and team threads inside a product.
- When delivery state, read receipts and who is typing matter.
- Its composer is exported separately as
ChatComposerfor a stand-alone message box (autosizing, attachments by button, paste or drop). - Use CommentThread for threaded, resolvable discussion on an object, and ChatPanel / PromptBar for AI prompt input.
Usage
import { ChatThread } from "@/components/iris/chat-thread/chat-thread";
h(
"div",
{ style: { height: 480 } }, // the thread fills its container
h(ChatThread, {
label: "Launch desk thread",
participants: [
{ id: "me", name: "Giulia Ferri" },
{ id: "katerina", name: "Katerina Vlachou", avatar: katerinaUrl },
],
currentUserId: "me",
defaultMessages: [
{
id: "m1",
authorId: "katerina",
text: "The Q3 launch post for LinkedIn is in review.",
createdAt: "2026-10-22T14:48:00Z",
},
{ id: "m2", authorId: "me", text: "Reading it now.", createdAt: "2026-10-22T14:52:00Z", status: "read" },
],
readBy: { katerina: "m2" },
typing: ["katerina"],
onSend: ({ text, files }) => api.send(text, files),
}),
);
Props
| Prop | Type | Default | Description |
|---|---|---|---|
participants |
{ id; name; avatar? }[] |
— (required) | Everyone in the thread. Initials show without avatar. |
currentUserId |
string |
— (required) | Whose messages sit on the right and carry status. |
messages / defaultMessages |
ChatMessage[] |
— | Oldest first. { id; authorId; text?; createdAt: Date | string | number; attachments?; reactions?; status?: "sending" | "sent" | "delivered" | "read" | "failed" }. Leave messages out to let the thread keep its own list. |
typing |
string[] |
— | Participant ids currently typing. |
readBy |
Record<participantId, messageId> |
— | Last message each person has read; their avatar sits under it and glides as it moves. |
onSend |
(draft: { text; files }) => void | Promise<void> |
— | Uncontrolled threads also append the message themselves. |
onReact / onRetry |
(messageId, emoji) / (messageId) |
— | Reaction toggled; retry of a failed message. |
reactions |
string[] |
six common emoji | Emoji offered by the reaction picker. |
composer / allowAttachments / accept |
boolean / boolean / string |
true / true / — |
Show the composer, the attach button, and which file types it offers. |
placeholder |
string |
"Message" |
Composer placeholder. |
groupWindow |
number |
5 minutes | Messages from one person closer than this (ms) share a group. |
locale / label / labels |
string / string / ChatThreadLabels |
"en-US" / "Conversation" / English |
Formatting locale, accessible name of the log, and every visible or announced word for localisation. |
Notes
- The message list is a
role="log"; typing and new messages are announced politely. Enter sends, Shift+Enter adds a line; the reaction picker supports arrow keys and Escape. - The root is
height: 100%: give the parent a fixed height. Incoming bubbles usehoverin light mode andsurfacein dark mode; in dark mode seat the thread onpagerather thansurfaceso they stand out. The list stays pinned to the newest message and shows a "new messages" pill when something lands out of view. - Forwards a ref with
scrollToBottom(smooth?)andfocusComposer(). - Motion (bubbles, receipts, the send button morph) is reduced to fades under
prefers-reduced-motion.