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