Chat

A ready-made conversation UI combining `ChatMessages` and `ChatInput` — pass a `messages` array in and handle `send`, no wiring between the two required.

html
<UiChat />

Messages

messages drives the whole conversation, oldest first. Chat is ChatMessages and ChatInput together, and the composer's draft and attachments are internal to it — so the only thing you handle is the send event, by pushing onto this array.

Today
Hey, have you tried the new Chat component yet?
9:14 AM
AL
Ada Lovelace
Not yet — what's different about it?
9:15 AM
Also, does it support reactions?
9:15 AM
It bundles ChatMessages and ChatInput together, so you don't have to wire them up yourself — and yes, hover a bubble to react.
9:15 AM
AL
Ada Lovelace
Nice, that'll save some boilerplate 🎉
9:16 AM

No wiring between the two components required

Using ChatMessages and ChatInput separately (see their own doc pages) means owning the draft ref, the attachments ref, and pushing onto the messages array yourself. Chat does all of that internally — you only ever see finished messages, via `send`.

Group window

groupWindow is how many minutes apart two consecutive messages from the same sender can be and still read as one run — five by default. Widen it for a slow, considered conversation; narrow it where a gap of a minute genuinely means a new thought. It's forwarded straight through to ChatMessages.

group-window="1" — the three-minute gap breaks the run
Today
AL
Ada Lovelace
Pushed the fix.
9:14 AM
Should be live shortly.
9:14 AM
AL
Ada Lovelace
…three minutes later.
9:17 AM
group-window="10" — the same messages stay grouped
Today
AL
Ada Lovelace
Pushed the fix.
9:14 AM
Should be live shortly.
9:14 AM
…three minutes later.
9:17 AM

Auto scroll

autoScroll follows the newest message as messages grows — but only when the reader was already near the bottom, so it never yanks them away from something they scrolled up to read. Turn it off for a transcript people are meant to browse rather than follow live.

Today
AL
Ada Lovelace
Scroll up, then add a message — the view stays put.
9:14 AM

Empty text

emptyText stands in for the list before there's anything in it. Say what would fill it — “No messages yet. Say hello.” — rather than only reporting the emptiness, since the composer right below is the answer.

No messages yet. Say hello.

Placeholder

placeholder is the hint in the empty composer, forwarded to the inner ChatInput. Naming the room or the person — “Message #design” — doubles as a reminder of where the message is about to go.

Today
Hey, have you tried the new Chat component yet?
9:14 AM

Disabled

disabled locks the composer outright: no typing, no attachments, no sending.

Today
Hey, have you tried the new Chat component yet?
9:14 AM

Loading

loading locks the composer's send button and swaps its icon for a spinner — for the window while a send request is in flight.

Today
Hey, have you tried the new Chat component yet?
9:14 AM

Variant

variant is forwarded to the composer, styling its ring and background the same way it would on a bare ChatInput.

Today
Hey, have you tried the new Chat component yet?
9:14 AM

Color

color themes the composer's send button, and doubles as the fallback bubble color for own messages that don't set a color of their own.

This bubble picks up the success color automatically

Status

status is the fallback delivery state for own messages that don't carry their own. It's rarely useful as a blanket default — real delivery differs message to message — so reach for it only when every message in the list genuinely shares one, and set status per message otherwise.

Falls back to a read status and a shorter reaction picker

Allow reactions

allowReactions is the fallback for messages that don't set their own, so setting it false turns reactions off across the whole conversation without touching a single message object. Worth doing wherever there's nowhere to store a reaction.

Today
Hey, have you tried the new Chat component yet?
9:14 AM
AL
Ada Lovelace
Not yet — what's different about it?
9:15 AM

Allow attachments

allowAttachments shows the composer's paperclip, forwarded to the inner ChatInput. Turn it off wherever files have nowhere to go, rather than accepting them and dropping them silently.

Today
Hey, have you tried the new Chat component yet?
9:14 AM

Allow emojis

allowEmojis shows the composer's emoji button, which inserts into the draft at the cursor. Turning this and allowAttachments both off leaves a plain text composer.

Today
Hey, have you tried the new Chat component yet?
9:14 AM

Multiple

multiple decides whether the composer's file picker takes more than one file at a time. Set it false where a message can only carry one thing, so the constraint lands in the dialog rather than in an error afterwards.

Today
Hey, have you tried the new Chat component yet?
9:14 AM

Accept

accept is the comma-separated list of file types the composer's picker offers — MIME types like image/* or extensions like .pdf. It filters the dialog rather than validating the result, so still check what actually arrives.

Today
Hey, have you tried the new Chat component yet?
9:14 AM

Max rows

maxRows is how tall the composer's textarea grows before it starts scrolling, six rows by default. Lower it in a narrow panel, where a long draft would otherwise push the conversation off the screen entirely.

Today
Hey, have you tried the new Chat component yet?
9:14 AM

API Reference

Generated from the component's source — props, slots and emits as the component actually declares them.

Props

Prop
Type
Default
Description
ChatMessageItem[]
[]
The messages to render, oldest first. Any message missing its own `color`/`status` (own messages only) or `allowReactions` falls back to this component's own prop of the same name — set them per-message to override the fallback for just that one.
number
5
Groups consecutive messages from the same sender into a tighter run when they're no more than this many minutes apart. Forwarded straight through to `ChatMessages`.
boolean
true
Scrolls to the newest message whenever `messages` grows, but only if the viewer was already scrolled near the bottom. Forwarded straight through to `ChatMessages`.
string
'No messages yet'
Text shown in place of the list when `messages` is empty.
string
'Message...'
Placeholder text shown when the composer is empty.
boolean
false
Disables the composer — textarea, attachment/emoji buttons, and send button.
boolean
false
Shows a loading state on the composer's send button (a spinning loader instead of the paper-plane icon) and disables it — e.g. while a previous message is still being sent.
"outline" | "soft"
'outline'
The visual style of the composer.
"neutral" | "primary" | "secondary" | "success" | "error" | "warning" | "info" | "pending"
'primary'
The color of the composer's send button, and the fallback bubble color for `own` messages that don't set their own `color`. Doesn't affect the composer's own ring/background — those stay neutral regardless, per `variant`.
"sending" | "sent" | "delivered" | "read" | "failed"
Fallback delivery status for `own` messages that don't set their own `status`. Rarely useful as a blanket default since real delivery state differs message to message — set it per-message instead unless every `own` message in this list genuinely shares the same status.
boolean
true
Fallback for messages that don't set their own `allowReactions` — set this to `false` to turn reactions off for the whole conversation without touching every message object.
boolean
true
Shows the paperclip button that opens the native file picker.
boolean
true
Shows the emoji button that opens a picker for inserting emoji into the draft.
boolean
true
Whether the file picker allows selecting more than one file at a time.
string
Comma-separated list of accepted file types (e.g. 'image/*', '.pdf'), passed straight through to the file input.
number
6
Maximum number of rows the composer's textarea grows to before it starts scrolling.

Slots

Slot
actions

Emits

Event
Payload
Description
toggle-reaction
[message: ChatMessageItem, emoji: string]
Forwarded from `ChatMessages` whose reaction pill or picker was clicked, along with the source message — update your `messages` array in response to reflect the new state.
send
[payload: { content: string; attachments: File[] }]
Fired when the composer's send button is clicked, or Enter is pressed without Shift, while there's a non-empty draft and/or at least one attachment — push a new message onto `messages` in response. The composer's own draft and attachments are already cleared for you by then.
remove-attachment
[file: File, index: number]
Forwarded from the composer when an attachment chip's remove button is clicked, along with its index in the composer's (internal) attachment list.

Types

ts
type Variant = "outline" | "soft";
ts
type Color = "neutral" | "primary" | "secondary" | "success" | "error" | "warning" | "info" | "pending";
ts
type MessageStatus = "sending" | "sent" | "delivered" | "read" | "failed";
ts
interface ChatMessageItem {
    /**
     * Unique identifier, used as the list key when rendered via `ChatMessages`. Falls back to the array index if omitted.
     */
    id?: string | number;

    /**
     * The plain-text message content. Ignored if the default slot is used instead.
     */
    content?: string;

    /**
     * Whether this message was sent by the current viewer — aligns it to the right and hides its avatar/name.
     * @defaultValue false
     */
    own?: boolean;

    /**
     * The sender's display name, shown above the bubble for non-own messages (unless `grouped`).
     */
    name?: string;

    /**
     * The sender's avatar image URL. Falls back to initials, same as `Avatar`.
     */
    avatarSrc?: string;

    /**
     * The sender's first name, used for the avatar's fallback initials.
     */
    firstName?: string;

    /**
     * The sender's last name, used for the avatar's fallback initials.
     */
    lastName?: string;

    /**
     * When to show this message: a `Date` is formatted as a local time (e.g. '10:32 AM'); a string is displayed as-is.
     */
    timestamp?: string | Date;

    /**
     * Delivery status, shown as a small icon next to the timestamp. Only rendered for `own` messages.
     */
    status?: ChatMessageStatus;

    /**
     * The bubble color for `own` messages. Has no effect on received messages, which are always neutral.
     * @defaultValue 'primary'
     */
    color?: ChatMessageColor;

    /**
     * Emoji reactions to show as pills below the bubble. Clicking one toggles it — the count and
     * `reacted` state are your responsibility to update in response to the `toggle-reaction` event.
     */
    reactions?: ChatMessageReaction[];

    /**
     * Shows a trigger (revealed on hover) that opens an emoji picker for reacting to the message.
     * @defaultValue true
     */
    allowReactions?: boolean;
}