ChatMessage

A single chat bubble — avatar, name, content, timestamp, and delivery status — aligned left or right depending on who sent it.

html
<UiChatMessage />

ID

id is a stable identifier for the message. This component doesn't use it for anything itself — it matters when you render a list yourself and need a :key, which is exactly what ChatMessages does with it, falling back to the array index when it's missing.

Ada Lovelace
The id is only ever a list key.
9:00 AM
Ada Lovelace
Nothing in the bubble reads it.
9:01 AM

Content

content is the message text, rendered as plain text. The default slot takes over completely when you use one, which is the route for anything richer than a sentence — a link, an image, a code block.

Plain text through the content prop.
9:04 AM
Custom rich content via the default slot.
9:05 AM

Own

own marks the message as sent by the current viewer rather than received: it aligns right, drops the avatar and name, and becomes the only kind of message that renders a status.

AL
Ada Lovelace
Have you looked at the new component yet?
10:32 AM
Just opened it, looks great!
10:33 AM

timestamp handles a Date and a string differently — a Date is formatted for you ('10:32 AM'); a string is displayed exactly as given, with no parsing. Pass a Date unless you specifically want custom text there.

The avatar gutter is reserved for received messages only, and is exactly the avatar's width — so a grouped message indents to line up under the avatar above it, and the gap to the bubble is the same gap-sm as everywhere else. Own messages get no gutter, because they never show an avatar and reserving the column only held the bubble off the edge.

Name

name is the sender's display name, printed above the bubble on received messages. It's dropped on own messages — you know who you are — and on grouped ones, where the run above already said it.

No name — the bubble stands alone.
9:00 AM
Ada Lovelace
With a name above the bubble.
9:01 AM

Avatar src

avatarSrc shows an image avatar in place of the initials, falling back to them if the image can't load — the same chain Avatar uses.

Ada Lovelace
Avatars fall back to initials, same as Avatar.
9:00 AM
Ada Lovelace
Ada Lovelace
This one's image is broken.
9:01 AM

First name

firstName feeds the avatar's fallback initials — it isn't printed anywhere, since name is what's displayed. Pass both when you have them structured; the avatar is the only thing that reads this.

Ada Lovelace
No first name — the avatar has nothing to build initials from.
9:00 AM
A
Ada Lovelace
With a first name.
9:01 AM

Last name

lastName is the second half of the avatar's fallback initials, so the circle reads “AL” rather than just “A”. Like firstName it's never rendered as text.

A
Ada Lovelace
One initial.
9:00 AM
AL
Ada Lovelace
Two initials.
9:01 AM

Timestamp

timestamp accepts either a Date, which is formatted as a local time like “10:32 AM”, or a string, which is printed exactly as given. Pass the Date when you have one — the formatting follows the reader's locale — and a string when you want something relative like “just now”.

Ada Lovelace
A real Date, formatted for the reader.
12:07 AM
Ada Lovelace
A string, printed as-is.
just now
Ada Lovelace
No timestamp at all.

Status

status shows a small delivery icon beside the timestamp. It only ever renders on own messages, matching how real chat apps report delivery for what you sent and not for what you received.

Sending...
Sent
Delivered
Read
Couldn't send this

The status icon (status prop) only ever renders for own messages — there's no equivalent for received ones, matching how every real chat app only shows delivery state for what *you* sent.

Color

color tints an own bubble. Received messages are always neutral, so this has no effect without own.

primary (default)
success
info
neutral

Grouped

grouped hides the avatar and name so a message reads as part of the run above it. Deciding which messages group is the caller's job — ChatMessages does it for you from the timestamps.

GH
Grace Hopper
Pushed the fix.
2:14 PM
Should be live in a few minutes.
2:14 PM
Let me know if you still see the issue.
2:15 PM

grouped is the only thing this component knows about grouping — it just hides the avatar/name when true. Deciding which messages in a conversation should be grouped is ChatMessages's job, not this component's.

Reactions

reactions renders emoji pills below the bubble, each an emoji, a count and a reacted flag that highlights the pill for the current viewer. Clicking one emits toggle-reaction — the component reports the change and leaves updating the count to you, since the real count lives on your server.

AL
Ada Lovelace
Have you looked at the new component yet?
10:32 AM
Just opened it, looks great!
10:33 AM

Allow reactions

allowReactions is the hover trigger that opens an emoji picker on the bubble, and it's on by default. Turn it off in a thread where reacting isn't a thing people do — a system log, a transcript — so the affordance doesn't promise something the backend can't store.

Ada Lovelace
Hover me — there's a react button.
10:32 AM
Ada Lovelace
Hover me — there isn't.
10:33 AM

API Reference

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

Props

Prop
Type
Default
Description
string | number
Unique identifier. Not used by this component directly — only relevant when rendering a list yourself, as `ChatMessages` does.
string
The plain-text message content. Ignored if the default slot is used instead.
boolean
false
Whether this message was sent by the current viewer — aligns it to the right and hides its avatar/name.
string
The sender's display name, shown above the bubble for non-own messages (unless `grouped`).
string
The sender's avatar image URL. Falls back to initials, same as `Avatar`.
string
The sender's first name, used for the avatar's fallback initials.
string
The sender's last name, used for the avatar's fallback initials.
string | Date
When to show this message: a `Date` is formatted as a local time (e.g. '10:32 AM'); a string is displayed as-is.
"sending" | "sent" | "delivered" | "read" | "failed"
Delivery status, shown as a small icon next to the timestamp. Only rendered for `own` messages.
"neutral" | "primary" | "secondary" | "success" | "error" | "warning" | "info" | "pending"
'primary'
The bubble color for `own` messages. Has no effect on received messages, which are always neutral.
boolean
false
Hides the avatar and name, and is meant to be paired with tighter spacing — set this when a message is part of a consecutive run from the same sender. `ChatMessages` computes this for you.
ChatMessageReaction[]
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.
boolean
true
Shows a trigger (revealed on hover) that opens an emoji picker for reacting to the message.

Slots

Slot
default

Emits

Event
Payload
Description
toggle-reaction
[emoji: string]
Fired when a reaction pill is clicked, or an emoji is picked from the reaction picker — either way, this only reports intent. Whether it adds/removes/increments/decrements is up to you.

Types

ts
type ChatMessageColor = "neutral" | "primary" | "secondary" | "success" | "error" | "warning" | "info" | "pending";
ts
type ChatMessageStatus = "sending" | "sent" | "delivered" | "read" | "failed";
ts
interface ChatMessageReaction {
    /**
     * The reaction emoji, e.g. '👍'.
     */
    emoji: string;

    /**
     * How many people reacted with this emoji.
     */
    count: number;

    /**
     * Whether the current viewer is among those who reacted with this emoji — highlights the pill.
     * @defaultValue false
     */
    reacted?: boolean;
}
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;
}