ChatMessages

A scrolling list of `ChatMessage`s that groups consecutive messages from the same sender, inserts date separators, and auto-scrolls to the newest message.

html
<UiChatMessages />

Messages

messages is the conversation, oldest first. Consecutive messages from one sender group into a run, a separator marks each new day, and hovering a bubble offers a reaction — all derived from the array, so there's nothing to wire up per message.

Yesterday
AL
Ada Lovelace
Hey, did you get a chance to review the PR?
4:02 PM
No rush, just checking in.
4:04 PM
Just finished it — looks solid, left a couple of small comments.
4:42 PM
Today
AL
Ada Lovelace
Perfect, I'll take a look this morning.
9:14 AM
Sounds good 👍
9:17 AM

Date separators ('Today', 'Yesterday', or a formatted date) only appear for messages that have a timestamp — a conversation with no timestamps at all renders with no separators, not one for every message. A string timestamp that isn't a parseable date (e.g. a bare '9:41 AM', which ChatMessage treats as opaque display text) is likewise treated as no timestamp for grouping/separator purposes — it still displays fine, it just won't drive either feature.

messages[].id is optional; when omitted the array index is used as the :key instead. Fine for a static demo, but you'll want real ids once messages can be reordered or removed, to avoid the usual key-reuse glitches.

This component only renders the list — it has no input of its own. Sending a message (see the example) is just pushing a new object onto whatever array you passed as messages.

Auto-scroll only kicks in near the bottom

Scroll up in the conversation above, then send a message — the list won't yank you back down, since you weren't already near the bottom. Stay near the bottom and it scrolls to follow new messages, same as any real chat app.

Group window

groupWindow is how many minutes apart two messages from the same sender can be and still read as one run. It's narrowed to a single minute below, so the pair breaks into two runs instead of one.

Today
AL
Ada Lovelace
First message.
9:14 AM
AL
Ada Lovelace
Five minutes later, ungrouped since it's past the 1-minute window.
9:19 AM

Grouping (hiding the repeated avatar/name for consecutive messages from the same sender) only considers time when both messages have a timestamp — set within groupWindow minutes of each other. Without timestamps on both, consecutive same-sender messages are grouped unconditionally, regardless of how far apart they are in the array.

Auto scroll

autoScroll follows the newest message and is on by default. Turn it off when the list isn't what the viewer is watching — a transcript being read back, say — and new messages should never move the viewport.

auto-scroll (default)

Today
AL
Ada Lovelace
Message 1
9:14 AM
Message 2
9:15 AM
AL
Ada Lovelace
Message 3
9:16 AM
Message 4
9:17 AM
AL
Ada Lovelace
Message 5
9:18 AM
Message 6
9:19 AM
AL
Ada Lovelace
Message 7
9:20 AM
Message 8
9:21 AM

:auto-scroll="false"

Today
AL
Ada Lovelace
Message 1
9:14 AM
Message 2
9:15 AM
AL
Ada Lovelace
Message 3
9:16 AM
Message 4
9:17 AM
AL
Ada Lovelace
Message 5
9:18 AM
Message 6
9:19 AM
AL
Ada Lovelace
Message 7
9:20 AM
Message 8
9:21 AM

Auto-scroll checks whether the viewer was already within ~80px of the bottom *before* the new message was added — if they'd scrolled up to read history, sending/receiving a new message won't yank them back down.

Empty text

emptyText stands in for the list while messages is empty. Say what would fill it rather than only reporting the emptiness.

No messages yet — say hi!


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.
number
5
Groups consecutive messages from the same sender into a tighter run — hiding the repeated avatar/name — when they're no more than this many minutes apart. Only takes effect when both messages have a `timestamp`; without one, consecutive same-sender messages are grouped regardless of how far apart they are.
boolean
true
Scrolls to the newest message whenever `messages` grows, but only if the viewer was already scrolled near the bottom — so it won't yank them away from something they're reading further up.
string
'No messages yet'
Text shown in place of the list when `messages` is empty.

Emits

Event
Payload
Description
toggle-reaction
[message: ChatMessageItem, emoji: string]
Forwarded from the underlying `ChatMessage` whose reaction pill or picker was clicked, along with the source message — update your `messages` array in response to reflect the new state.

Types

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;
}