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.json
import { ChatThread } from "@/components/iris/chat-thread/chat-thread";
Registry item chat-threadBrings in foundationRegistry JSON

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 ChatComposer for 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 use hover in light mode and surface in dark mode; in dark mode seat the thread on page rather than surface so 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?) and focusComposer().
  • Motion (bubbles, receipts, the send button morph) is reduced to fades under prefers-reduced-motion.