A conversation that stays pinned to the newest message unless the reader scrolls up.
Example
import { MessageList, MessageListEmpty, MessageListDownload } from "@elements/ui/message-list";
import { Message, MessageContent, MessageResponse } from "@elements/ui/message";
<MessageList busy={reply.streaming}>
<MessageListEmpty e:if={messages.length === 0} />
<Message e:for={m of messages} e:key={(m) => m.id} from={m.role}>
<MessageContent><MessageResponse markdown={m.text} /></MessageContent>
</Message>
<MessageListDownload slot="overlay" messages={messages} />
</MessageList>
Parts
| Part |
What it is |
MessageList |
The scroller. Fills its container's height and follows new content while at the bottom. |
.message-list-viewport |
role=log, focusable so the keyboard can scroll it. |
.message-list-content |
The messages, spaced by --message-list-gap; grouped follow-ups sit closer. |
.message-list-scroll |
Scroll to latest: shown when not at the bottom, floating over the messages. |
MessageListEmpty |
A centered placeholder for an empty conversation. |
MessageListDownload |
Saves the conversation as a Markdown file. |
messagesToMarkdown() |
The Markdown the download writes; takes an optional formatter. |
Attributes
MessageList
| Attribute |
Type |
Default |
Description |
label |
string |
"Conversation" |
The log's accessible name. |
stickToBottom |
boolean |
true |
Follow new content while at the bottom. |
initial |
"smooth" | "instant" | "none" | { damping, stiffness, mass } |
"smooth" |
How it reaches the newest message on mount. |
resize |
"smooth" | "instant" | { damping, stiffness, mass } |
"smooth" |
How it follows growing content: native smooth, instant, or a spring (defaults 0.7, 0.05, 1.25). Reduced motion makes every mode instant. |
contentClass |
string |
"" |
Extra classes on the content wrapper. |
contentAttrs |
Record<string, string | number | boolean | undefined> |
{} |
Attributes spread on the content wrapper. |
threshold |
number |
70 |
Pixels from the end that still count as at the bottom. |
targetScrollTop |
((target, { scrollElement, contentElement }) => number) | null |
null |
Moves the bottom the list follows, such as the top of the newest reply; clamped to the scroll range. |
atBottom |
boolean (two-way) |
true |
False once the reader scrolls up. Set it true to scroll down and follow again. |
busy |
boolean |
false |
aria-busy on the log while a reply streams, so it is announced once, finished. |
scrollButton |
boolean |
true |
Render the scroll to latest button. |
scrollButtonLabel |
string |
"Scroll to latest" |
Its accessible name. |
scrollButtonVariant |
"primary" | "secondary" | "outline" | "ghost" | "link" | "danger" |
"outline" |
The scroll button's variant. |
scrollButtonClass |
string |
"" |
Appended to the scroll button's class. |
scrollButtonAttrs |
Record<string, string | number | boolean | undefined> |
{} |
Attributes spread on the scroll button. |
onAtBottomChange |
(atBottom: boolean) => void |
() => {} |
Fires when the reader leaves or reaches the bottom. |
class |
string |
"" |
Extra classes on the root. |
attrs |
Record<string, string | number | boolean | undefined> |
{} |
Other attributes for the root. |
MessageListEmpty
| Attribute |
Type |
Default |
Description |
title |
string |
"No messages yet" |
Heading. |
description |
string |
"Start a conversation to see messages here" |
Text under it. |
MessageListDownload
| Attribute |
Type |
Default |
Description |
messages |
ChatMessageLike[] |
(required) |
{ role, text?, content?, parts? }. Messages from common AI chat toolkits, with text in parts, work as they are. |
filename |
string |
"conversation.md" |
The saved file's name. |
formatMessage |
(message, index) => string |
**Role:** text |
Formats each message. |
label |
string |
"Download conversation" |
Accessible name and tooltip. |
Slots
| Slot |
Part |
Content |
default |
MessageList |
Messages, or a MessageListEmpty. |
overlay |
MessageList |
Floating controls at the top inline end, such as MessageListDownload. |
scroll-icon |
MessageList |
Replaces the scroll button's arrow icon. |
icon |
MessageListEmpty |
An icon above the title. |
default |
MessageListEmpty |
Extra content under the text, such as suggestions. |
default |
MessageListDownload |
A custom icon. |
Keyboard
| Keys |
Action |
Tab |
Focuses the log, a scrollable region. |
ArrowUp / ArrowDown |
Scroll the focused log. Scrolling up stops following new messages. |
PageUp / PageDown |
Scroll by a page. |
Home / End |
Jump to the oldest or newest message. End follows again. |
Enter / Space |
On the scroll button: back to the newest message, following again; focus moves to the log as the button hides. |
Data attributes
| Attribute |
Values |
On |
data-at-bottom |
present at the bottom |
.message-list |
data-stuck |
present while following new content |
.message-list |
Accessibility
- The viewport is role=log (a polite live region) with an aria-label, so each new message is announced.
- busy sets aria-busy while a reply streams; screen readers read the finished text instead of every token.
- The viewport is focusable, so keyboard users can scroll the conversation.
- The scroll button is named Scroll to latest and leaves the tab order while hidden.
See also
- Message (opens in a new tab): One chat message with avatar, author, time, streaming Markdown, attachments, actions and response branches.
- PromptInput (opens in a new tab): A growing message composer with attachments, tools, a model picker and a send button that stops a streaming reply.
- Attachment (opens in a new tab): A file or source as a thumbnail, chip or row, with preview, size, upload progress and remove.