TrademarkTrademark
Docs
Ctrl k
Search docs...
Modules

Chat

Updated: October 17, 2026
View on GitHub

Five components cover the parts every chat has: ChatMessage, ChatMessageList, ChatComposer, TypingIndicator and ConversationItem. They work for conversations between people and for an assistant that streams its replies.

ChatMessage

One message: the bubble, its author, the time and the delivery state. from="self" puts it on the right with the brand tint. Set grouped on a follow-up from the same author: the name is dropped and the avatar keeps its space, so the bubbles line up. A Date in time is formatted for the reader and rendered as a <time> element.

variant="plain" drops the bubble for a flat, team-chat layout where every message reads left to right. attachments takes media or link previews under the text, footer takes reactions, and actions takes controls that show while the message is hovered or focused (they are always shown on touch screens).

Type
Default
children
The message body: text, or any content.
React.ReactNode—
from
Whose message it is. `self` sits on the right.
selfother
other
name
The author's name, shown above the first message of a group.
ReactNode—
avatarProps
The author's avatar. Leave it out for no avatar column.
AvatarProps—
time
When it was sent. A `Date` is formatted; anything else renders as given.
undefinednullstringnumberbigintfalsetrueReactElement<unknown, string | JSXElementConstructor<any>>Iterable<ReactNode>ReactPortalPromise<AwaitedReactNode>Date
—
status
Delivery state, shown next to the time.
sendingsentdeliveredreadfailed
—
statusLabels
Replaces the status words, for another language.
Partial<Record<ChatMessageStatus, string>>—
onRetry
Adds a retry button to a failed message.
() => void—
retryLabel
The retry button's label.
stringRetry
grouped
A follow-up from the same author.
booleanfalse
variant
A tinted bubble, or a flat row.
bubbleplain
bubble
attachments
Content under the bubble.
ReactNode—
footer
A row under the attachments, such as reactions.
ReactNode—
actions
Controls shown on hover or focus.
ReactNode—
…and every FlexProps propinherited—

ChatMessageList

The scrolling column a conversation lives in. It starts at the newest message and follows new ones, including content that grows on its own like a streamed reply, while the reader is at the bottom. Once they scroll up to read history it stays where they are. onReachStart fires when they reach the top, to load older messages. The list is a log live region, so screen readers announce new messages.

Give it a parent with a fixed or flexible height; it fills the space and scrolls inside it.

Type
Default
stickToBottom
Start at the newest message and follow new ones while the reader is at the bottom.
booleantrue
onReachStart
Called when the reader scrolls to the top.
() => void—
ariaLabel
The list's accessible name, such as who the chat is with.
stringMessages
…and every FlexProps propinherited—

ChatComposer

The input bar. The field grows with the draft up to about eight lines, Enter sends and Shift+Enter starts a new line (an Enter that confirms an IME composition never sends). onSend gets the trimmed text and the attached files. If it returns a promise the send button shows loading; the field clears when the promise resolves and keeps the draft when it rejects, so showing the error is up to you.

attach adds a button that opens the file picker. Pasted images are attached too. Pass { accept, multiple } to limit what can be picked.

Type
Default
onSendrequired
Called with `{ text, files }`. Return a promise to show loading.
(message: ChatComposerMessage) => void | Promise<void>—
value
The draft, when the parent owns it.
string—
defaultValue
The first draft, when the composer owns it.
string""
onValueChange
Called on every edit.
(value: string) => void—
placeholder
Text shown in the empty field.
stringWrite a message
attach
Show the attach button; an object sets `accept` and `multiple`.
boolean{ accept?: string; multiple?: boolean }
false
sendOnEnter
Enter sends. Off, Enter starts a new line.
booleantrue
loading
Show the send button loading and block another send.
booleanfalse
disabled
Disable the field and buttons.
booleanfalse
ariaLabel
The field's accessible name.
stringMessage
sendLabel
The send button's label.
stringSend
attachLabel
The attach button's label.
stringAttach files
removeLabel
The label of an attachment's remove button, from its file name.
(fileName: string) => string(fileName) => `Remove ${fileName}`
prefix
Content before the field, such as an emoji picker.
ReactNode—
actions
More controls before the send button.
ReactNode—
maxLength
The longest draft allowed.
number—
id
The field's id. One is generated when it is left out.
string—
…and every FlexProps propinherited—

TypingIndicator

Three pulsing dots for someone typing or a reply being generated. It is a status region, so its label is announced once. With reduced motion the dots fade instead of bouncing.

Type
Default
label
Visible text next to the dots.
ReactNode—
ariaLabel
What a screen reader announces when there is no label.
stringTyping
bubble
Draw the dots in a bubble, to stand in for a reply.
booleantrue
…and every FlexProps propinherited—

ConversationItem

One row of an inbox: the avatar, the name, the last message and its time, and an unread count. It is a button with onClick and a link with href, and selected marks the open conversation with aria-current. A Date in time shows the time today, the weekday this week and the date before that.

Type
Default
namerequired
Who the conversation is with, or its title.
ReactNode—
preview
The last message, cut to one line.
ReactNode—
time
When the last message was sent.
undefinednullstringnumberbigintfalsetrueReactElement<unknown, string | JSXElementConstructor<any>>Iterable<ReactNode>ReactPortalPromise<AwaitedReactNode>Date
—
avatarProps
The avatar on the left.
AvatarProps—
unread
Unread messages: a number shows a count, `true` a dot.
numberboolean
—
unreadLabel
What a screen reader hears for the unread count.
(count: number | true) => stringdefaultUnreadLabel
selected
The open conversation.
booleanfalse
typing
Show the typing label in place of the preview.
booleanfalse
typingLabel
The typing label.
ReactNodetyping…
href
Open the conversation as a link.
string—
onClick
Open the conversation as a button.
(event: React.MouseEvent<HTMLElement>) => void—
…and every FlexProps propinherited—
Edit this page on GitHub
TrademarkTrademark
Built with curiosity by Lorant One and the Design Engineers Club