组件 · 消息
MessageList
The scrolling timeline of a conversation or thread, with the date lines, the notice at its start, the marker for limited history and the jump controls that float over it.
import { MessageList } from "@botiverse/raft-ui";MessageList is the scroll container of a conversation: it holds the messages, event lines and dates in one column, newest at the bottom (Conversation). It sits in the body of a ConversationPanel or a ThreadPanel and is the only part of the panel that scrolls.
When to use
| Situation | Use |
|---|---|
| The messages of a conversation or a thread | MessageList |
| One message | MessageItem |
| An action that happened: a claim, a hand-off | SystemMessage, inside the list |
| An agent's history of actions, outside a conversation | Timeline |
| Any other region that scrolls | ScrollArea, or the panel's own PanelScrollViewport |
Anatomy
MessageList: the scroll container. Its props andrefgo to the element that scrolls, so the caller can read and set the scroll position;rootClassNamestyles the outer element.MessageListNotice: a centered line in Space Mono at the start of the history, such as "Beginning of messages".tone="faint"(the default) sets it inforeground-hint,tone="muted"inforeground-muted.MessageListHistoryLimited: a small chip with aline-hairlineoutline, in Space Mono, for a history that doesn't go back to the start.MessageListDateDivider: the date between twoline-hairlinerules, intext-mono-metaandforeground-hint, drawn like an event line (SystemMessage). It is a boundary: its rule sits the section step, 24px, from the text above and below it (Timeline rhythm).- The jump controls (Jump controls), experimental, exported from
@botiverse/raft-ui/wip:MessageListJumpControls: where the pills float, centered on the list's bottom edge, or its top withside="top". Put it besideMessageList, in the same positioned parent (ConversationPanelBodyis one).MessageListJumpPill: one pill, a Button atoutlineandmdbuilt from the Button recipe: its icon, then its label.MessageListJumpPillGroupwithMessageListJumpPillDismiss: a pill with a dismiss button joined to its end on one shared edge, like a split button. The dismiss button has a 16px icon inforeground-mutedand no edge of its own on the pill's side.
Behavior
- The list only scrolls up and down. Wide content, such as a long line of code or a row of tabs, scrolls inside its own container and never widens the messages.
- On desktop the list is a ScrollArea: its thin scrollbar shows only while the list scrolls.
- Below 768px it uses the panel's native scrolling, without the drawn scrollbar (Responsive layout).
- Scrolling reaches the end of the list and stops; it doesn't scroll the page behind it.
- MessageList renders every message it is given and doesn't load older ones. For a history too long to keep in the DOM, use VirtualList, which renders the rows in view and keeps them still while older rows load above; loading them stays in the product.
Jump controls
When the person has scrolled away from the end, or there is somewhere to go that is out of view, a pill floats over the list and takes them there.
On a phone two pills rarely fit in one row. They stack, 12px apart so their hit areas don't overlap, with the first one nearest the edge:
- Show Back to Bottom while the end of the conversation is out of view; when messages arrived since the person scrolled away, the same pill says how many: "3 New Messages". Remove it once the end is in view again.
- Put a pill that goes to something further down at the bottom, after Back to Bottom: "Next Unread Thread (3)". Put one that goes up, "Earliest Unread Thread (3)", alone at the top.
- Show at most two pills at the bottom and one at the top. A third destination waits until one of them is gone: more than two pills cover the messages they are meant to reach.
- Give a pill a dismiss button only when the person may not want to go where it points (unread threads), never Back to Bottom. A dismissed pill stays away until its count changes.
- Write the label in Title Case, as on any button, with the count in it: "3 New Messages", "Next Unread Thread (3)".
How they look
A pill is one action, so it is a Button, at the size of the ordinary buttons around Raft (Save in a settings row): outline at md, with the label in text-control at 700, a 16px icon and a 32px height (Button). It is built from the Button recipe, so it can't drift from it. How it sits with the other controls that float in a conversation is in Conversation.
- Float the controls 12px from the edge of the list, centered, with 8px between pills. At the bottom they rise by
--message-list-bottom-inset, which the product sets to the height of whatever else floats on that edge (the line of who is about to post, Working agents), so a pill never covers it. - Draw no shadow. A pill is a control, marked by its ink edge on
layer-card, and the opaque card keeps it readable over the messages that pass under it (Elevation). - Make a pill appear and disappear without animation, and replace its count without rolling the digits (Motion): it doesn't come from anywhere, and a pill that slides draws the eye from the messages each time someone scrolls.
- Keep
MessageListJumpControlsmounted and change only what is in it, or hide it withhidden. Adding and removing the list's sibling while someone scrolls makes WebKit restyle the whole list. - Every pill and the dismiss button keep a 44px hit area, which reaches past the 12px edge only by 6px.
Content
- Write the notice in sentence case and say what it is the start of: "Beginning of messages", "Beginning of replies".
Accessibility
- MessageList renders plain elements and adds no role or live region. Name the panel around it after the conversation (ConversationPanel).
- The notice, the date lines and the history marker are text, not controls.
- Put the date in
MessageListDateDividerin a<time>element withdateTime, as for an event line's time (SystemMessage). - The jump pills are native buttons; their label is their name. Give
MessageListJumpPillDismissanaria-labelin Title Case that says what it dismisses when there is more than one: "Dismiss Unread Threads". - The pills come after the list in the DOM, so Tab reaches them after the messages, and they are not announced when they appear: they repeat what scrolling shows.
Rules
- Leave room above the first message for its hover toolbar (MessageItem). A list that opens with a date line already has it.
- Start the list a section step, 24px, above its first line (
pt-timeline-startover the first row; the notice brings a row's lead like a message, so the step holds whichever comes first), and end it so the composer's frame sits a section step under the last line (pb-timeline-endunder the last row, when the composer adds no space of its own). MessageList leaves both to the product, which knows what sits above and below the list (Timeline rhythm). - Give any other line that marks a boundary in the list, such as a new-messages line, the section step on both sides, like the date line.
- Show one thing above the oldest loaded message: a notice while older messages load, the history marker when the history is cut short, or "Beginning of messages" once the start is reached.
API
MessageList
| Prop | 类型 | 默认值 |
|---|---|---|
| string | ||
| Allows you to replace the component's HTML element with a different tag, or compose it with another component. | ReactElement | ((props, state) => ReactElement) |
Also takes the attributes of its element.
MessageListDateDivider, MessageListHistoryLimited
| Prop | 类型 | 默认值 |
|---|---|---|
| Allows you to replace the component's HTML element with a different tag, or compose it with another component. | ReactElement | ((props, state) => ReactElement) |
Also takes the attributes of its element.
MessageListNotice
| Prop | 类型 | 默认值 |
|---|---|---|
| "muted" | "faint" | "faint" | |
| Allows you to replace the component's HTML element with a different tag, or compose it with another component. | ReactElement | ((props, state) => ReactElement) |
Also takes the attributes of its element.
MessageListJumpControls
| Prop | 类型 | 默认值 |
|---|---|---|
| The edge of the list the controls float on. | "top" | "bottom" | "bottom" |
| Allows you to replace the component's HTML element with a different tag, or compose it with another component. | ReactElement | ((props, state) => ReactElement) |
Also takes the attributes of its element.
MessageListJumpPill
| Prop | 类型 | 默认值 |
|---|---|---|
| Whether the button should be focusable when disabled. | boolean | false |
| Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`). | boolean | true |
| Allows you to replace the component's HTML element with a different tag, or compose it with another component. | ReactElement | ((props, state) => ReactElement) |
Also takes the attributes of its element.
MessageListJumpPillDismiss
| Prop | 类型 | 默认值 |
|---|---|---|
| Defines a string value that labels the current element. What the button does, in Title Case: the product's words. | string | |
| Whether the button should be focusable when disabled. | boolean | false |
| Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`). | boolean | true |
| Allows you to replace the component's HTML element with a different tag, or compose it with another component. | ReactElement | ((props, state) => ReactElement) |
Also takes the attributes of its element.
MessageListJumpPillGroup
| Prop | 类型 | 默认值 |
|---|---|---|
| Allows you to replace the component's HTML element with a different tag, or compose it with another component. | ReactElement | ((props, state) => ReactElement) |
Also takes the attributes of its element.
相关组件
改动
- 0.12.0 · AddedJump controls that float over a conversation, from
@botiverse/raft-ui/wip:MessageListJumpControls,MessageListJumpPill(a Buttonoutline md),MessageListJumpPillGroupandMessageListJumpPillDismiss. - 0.5.53 · Fixed
MessageListno longer lets wide content (aw-maxtab strip, a long code line) stretch every message past a narrow list on a desktop viewport: itsScrollAreaContentresets Base UI's inlinemin-width: fit-contentto 0, so the content keeps the list width and wide children scroll inside their own box.