Xzero UI
Pro library

Chat thread

A chat thread with grouped messages, reactions, read receipts, typing, and a composer with attachments.

$ pnpm dlx shadcn@4.23.0 add @xzero-ui/chat-thread
Live · keyboard ready

When to use

When not to use

Installation

Register both Xzero UI namespaces in your project's components.json:

{
  "registries": {
    "@xzero-ui": "https://mcp.xzero.co.uk/r/{name}.json",
    "@xzero-ui-pro": "https://mcp.xzero.co.uk/r/pro/{name}.json"
  }
}
npx shadcn@4.23.0 add @xzero-ui/chat-thread

Install xzero-foundation once, and import @/components/xzero/foundation.css in your app root. No Xzero token is used by this snapshot. For sample portraits, photos, fonts, or logos, download https://mcp.xzero.co.uk/install-assets.mjs and run node xzero-install-assets.mjs <receiving-project>.

Usage

import { ChatThread, type ChatMessage } from "@/components/xzero/chat-thread/chat-thread";

const participants = [
  { id: "me", name: "Alex Rivera" },
  { id: "sam", name: "Sam Lee", avatar: "/avatars/sam.jpg" },
];

const messages: ChatMessage[] = [
  { id: "m1", authorId: "sam", text: "Is the build green?", createdAt: "2026-09-23T09:12:00Z" },
  { id: "m2", authorId: "me", text: "Yes, shipping now.", createdAt: "2026-09-23T09:13:00Z", status: "delivered" },
];

export function SupportChat() {
  return (
    <div style={{ height: 520 }}>
      <ChatThread
        participants={participants}
        currentUserId="me"
        defaultMessages={messages}
        readBy={{ sam: "m2" }}
        typing={["sam"]}
        onSend={draft => console.log(draft.text, draft.files)}
      />
    </div>
  );
}

API reference

ChatThread

A chat or support thread with grouped messages, day separators, reactions, read receipts, a typing indicator, and a composer. It stays pinned to the newest message and shows a pill when new messages land out of view.

PropTypeDefaultDescription
participants (required)ChatParticipant[]–Everyone in the thread: id, name, and optional avatar URL. Initials show without an avatar.
currentUserId (required)string–Id of the viewer. Their messages sit on the right in the accent color.
messagesChatMessage[]–Controlled messages, oldest first. Leave it out to let the thread keep its own list.
defaultMessagesChatMessage[]–Initial messages when uncontrolled.
typingstring[][]Participant ids currently typing. The current user is ignored.
readByRecord<string, string>–The last message each participant has read, by participant id. Their avatar sits under that message and glides as it moves.
onSend(draft: ChatDraft) => void | Promise<void>–Called with the composer's text and files. Uncontrolled threads also append the message themselves with status sent.
onReact(messageId: string, emoji: string) => void–Called when a reaction is picked or toggled. Uncontrolled threads update counts themselves.
onRetry(messageId: string) => void–Shows a Retry button under the last own message when its status is failed.
reactionsstring[]["👍", "❤️", "😂", "🎉", "👀", "🙏"]Emoji offered by the reaction picker.
placeholderstring"Message"Composer placeholder, also its accessible name.
composerbooleantrueShow the composer. Turn it off for a read-only transcript.
allowAttachmentsbooleantrueAllow files by button, paste, or drop in the composer.
acceptstring–File types the attach button offers, as for <input accept>.
groupWindownumber300000Messages from one person closer together than this, in ms, share a group. Defaults to five minutes.
localestring"en-US"Locale for times and day labels.
labelstring"Conversation"Accessible name of the message log.
labelsChatThreadLabels–Words for localization: sending, sent, delivered, read, failed, retry, today, yesterday, jumpToLatest, unknownAuthor, addReaction, reactions, and the composer's send, attach and attachments. Words with a count or names are functions: readBy, typing, newMessages, reactWith, reactionCount and removeFile. Any key left out keeps its English default; today and yesterday default to the locale's own words.
classNamestring–Extra class on the root.

ChatThreadHandle

Methods on the ref.

PropTypeDefaultDescription
scrollToBottom(smooth?: boolean) => void–Scrolls to the newest message and pins the thread there. Smooth by default, instant with reduced motion.
focusComposer() => void–Focuses the composer textarea.

ChatComposer

The message box on its own: autosizing text up to six lines, attachments by button, paste, or drop, and a send button that lifts its arrow on send.

PropTypeDefaultDescription
onSend (required)(draft: ChatDraft) => void–Called with trimmed text and the attached files. The composer clears itself after.
placeholderstring"Message"Placeholder and accessible name of the textarea.
allowAttachmentsbooleantrueShow the attach button and accept pasted or dropped files, up to ten at a time.
acceptstring–File types the attach button offers.
disabledboolean–Disables the textarea and attach button.
labelsChatComposerLabels–Words for localization: send, attach, attachments, and removeFile(name). Any key left out keeps its English default.
classNamestring–Extra class on the composer.

ChatMessage

Shape of one message.

PropTypeDefaultDescription
id (required)string–Stable id. Also the key used by readBy and onReact.
authorId (required)string–Participant id of the author.
textstring–Message body, rendered as plain text with line breaks kept.
createdAt (required)Date | string | number–Send time. Drives grouping, day separators, and the time label.
attachmentsChatAttachment[]–Images (kind image with url) show in a gallery of up to four with a +N tile; everything else shows as a download row. Pass width and height to reserve space for a single image.
reactionsChatReaction[]–Emoji, count, and mine for reactions the viewer added.
status"sending" | "sent" | "delivered" | "read" | "failed"–Delivery state of the viewer's own messages. Shown under their latest message only.

formatBytes

Formats a byte count as B, KB, or MB, as used on file rows.

PropTypeDefaultDescription
bytesnumber0Size in bytes.

Keyboard interactions

KeysAction
EnterSends the message from the composer. Ignored during IME composition.
Shift+EnterAdds a line in the composer.
TabReaches the add reaction button of each message; it shows on focus within the row.
Arrow Left / Arrow RightMoves between emoji in an open reaction picker, wrapping at the ends.
EscapeCloses the reaction picker and returns focus to its button.

Accessibility

Motion

Responsive behavior

Performance

Notes for AI

Also in activity

Guidance for AI tools

Chat thread: A chat thread with grouped messages, reactions, read receipts, typing, and a composer with attachments. Follow the declared prop types and do not invent props. Keep keyboard access, reduced motion support, and both light and dark themes intact when adapting it.

Full library index: https://ui.xzero.co.uk/llms.txt

Browse all components