Skip to content
+

Chat - Message list

Display messages in a scrollable, auto-scrolling list with date dividers, message groups, and streaming indicators.

The message list is the scrollable region that renders conversation history. ChatMessageList provides Material UI styling—scroll behavior, overflow, padding, and a thin scrollbar are handled automatically.

Import

import { ChatMessageList } from '@mui/x-chat';

Component anatomy

Inside ChatBox, the message list renders a subtree of themed components:

ChatMessageList                     ← scrollable container
  MessageListDateDivider            ← date separator between message groups (opt-in)
  ChatMessageGroup                  ← groups consecutive same-author messages
    ChatMessage                     ← individual message row
      ChatMessageAvatar             ← author avatar
      ChatMessageContent            ← message bubble with part renderers
      ChatMessageMeta               ← timestamp, delivery status
      ChatMessageActions            ← hover action buttons

Auto-scrolling

The message list automatically scrolls to the bottom when:

  • The user sends a new message (always active).
  • New messages arrive from the assistant while the user is near the bottom.
  • Streaming content grows (token-by-token updates).

The auto-scroll behavior is gated by a buffer—if the user has scrolled more than buffer pixels away from the bottom, automatic scrolling pauses so the user can read earlier messages without interruption.

Configuration

Control auto-scrolling through the features prop on ChatBox. Use the toggle in the demo below to compare the behavior with auto-scroll enabled and disabled. When auto-scroll is disabled, the user can still scroll to the bottom manually using the scroll-to-bottom affordance button.

Material UI chat

Styled with your active MUI theme

MUI Assistant
MUI Assistant

Hello! I am styled using your active Material UI theme. Try sending a message.

You
You

Great — the bubble colors come from palette.primary and the typography from the theme.

Scroll-to-bottom affordance

A floating button appears when the user scrolls away from the bottom. Clicking it smoothly scrolls back to the latest message:

{
  /* Enabled by default; disable with: */
}
<ChatBox adapter={adapter} features={{ scrollToBottom: false }} />;

History loading

When the user scrolls to the top of the message list, older messages are loaded automatically via the adapter's listMessages method. The message list preserves the current scroll position during prepend so the user doesn't lose their place.

Date dividers

When consecutive messages span different calendar dates, a date divider can be rendered between them. The divider shows a localized date string and is styled as a centered label with horizontal rules. Dividers are disabled by default—enable them with features={{ dateDivider: true }}.

Customize the date format through slotProps. The demo below enables the feature and uses a short month + day format:

Date divider demo

Custom date formatting

MUI Assistant
MUI Assistant

Here is a message from two days ago.

You
You

And this one is from yesterday.

MUI Assistant
MUI Assistant

This message is from today. Notice the short date format in the dividers above.

Message groups

Consecutive messages from the same author are grouped together into a ChatMessageGroup. Within a group only the first message displays the avatar, reducing visual repetition and making the conversation easier to scan. If no avatar resolves for that author, the avatar slot is omitted entirely.

The grouping window defaults to 5 minutes (300,000 ms). Customize it through slotProps. The demo below sets the window to 1 minute (60,000 ms)—messages more than 1 minute apart start a new group with a fresh avatar:

Message grouping demo

Custom grouping window

You
You

First message from the user.

Second message, sent 30 seconds later. Same group because the window is 1 minute.

You
You

Third message, sent 2 minutes after the first. This starts a new group.

MUI Assistant
MUI Assistant

With createTimeWindowGroupKey(60 000), consecutive messages from the same author are grouped only when they are less than 1 minute apart. The avatar appears only on the first message in each group.

Compact variant

Set variant="compact" on ChatBox to switch to a dense, messenger-style layout. Compact mode applies the following changes to the message list:

  • No bubbles—messages render as plain text without background colors or padding.
  • Left-aligned—all messages are left-aligned regardless of role (no right-aligned user messages).
  • Group header timestamps—the timestamp moves from below each message to the group header, displayed next to the author name.
  • Avatars preserved when available—the first message in each group still shows its resolved avatar.

When set on ChatBox, the variant automatically applies to the conversation list as well.

Team standup

Daily sync

MUI Assistant
MUI Assistant

Good morning! Here is the agenda for today.

We need to review the sprint progress and plan next steps.

You
You

Sounds good. I finished the variant feature yesterday.

The compact layout is ready for review.

MUI Assistant
MUI Assistant

Great work! The compact variant removes message bubbles and aligns everything to the left — perfect for dense message feeds.

<ChatBox variant="compact" adapter={adapter} />

Density

The density prop controls the vertical spacing between messages. Three values are available—compact, standard (default), and comfortable—mirroring the density model used in Data Grid—Density.

Use the toggle in the demo below to compare the three density levels:

Design review

UI team

MUI Assistant
MUI Assistant

Hey! I just pushed the updated mockups for the settings page.

Let me know what you think about the new spacing.

You
You

Looks great! The layout feels much more balanced now.

One thing: can we increase the gap between the sections?

MUI Assistant
MUI Assistant

Sure, I will add more vertical breathing room. Give me 10 minutes.

You
You

Perfect, take your time!

<ChatBox density="compact" adapter={adapter} />
<ChatBox density="comfortable" adapter={adapter} />

The density prop is independent of variant—combine variant="compact" with any density value.

Loading and streaming states

While the assistant is generating a response, streaming tokens are rendered incrementally inside a ChatMessageContent bubble. The message list auto-scrolls to follow new content as long as the user is near the bottom.

Standalone usage

When building a custom layout outside of ChatBox, use ChatMessageList directly inside a ChatRoot provider. The demo below renders only the message list with a placeholder for a custom composer:

MUI Assistant
MUI Assistant

Hello! I am styled using your active Material UI theme. Try sending a message.

You
You

Great — the bubble colors come from palette.primary and the typography from the theme.

Imperative scrolling

The ChatMessageList exposes a ref handle for imperative scroll control:

import { ChatMessageList } from '@mui/x-chat';
import type { MessageListRootHandle } from '@mui/x-chat/headless';

const listRef = React.useRef<MessageListRootHandle>(null);

// Scroll to bottom programmatically
listRef.current?.scrollToBottom({ behavior: 'smooth' });

<ChatMessageList ref={listRef} />;

Accessing scroll state with context

Child components inside the message list can access scroll state via context:

import { useMessageListContext } from '@mui/x-chat/headless';

function CustomScrollIndicator() {
  const { isAtBottom, unseenMessageCount, scrollToBottom } = useMessageListContext();

  if (isAtBottom) return null;
  return (
    <button onClick={() => scrollToBottom({ behavior: 'smooth' })}>
      {unseenMessageCount} new messages
    </button>
  );
}
Property Type Description
isAtBottom boolean Whether the scroll position is at the bottom
unseenMessageCount number Messages added since the user scrolled away
scrollToBottom (options?) => void Scroll to the latest message

Accessibility

Keyboard navigation

The message list is a single Tab stop: a roving tabindex over the role="article" messages keeps only one message in the tab order at a time, so tabbing from the composer to the rest of the application never walks through every message.

Tab into the list (a single stop), Arrow Up/Down between messages, Enter to drill into the focused message's links and buttons, Escape to come back, Tab onward to the composer.

Material UI chat

Styled with your active MUI theme

You
You

How do I move around this chat with the keyboard?

MUI Assistant
MUI Assistant

The message list is a single Tab stop — Arrow Up and Arrow Down move between messages.

You
You

And how do I reach a link or a copy button inside a message?

MUI Assistant
MUI Assistant

Press Enter on the focused message to drill into its controls, and Escape to come back. Try it here — this message has a link and a code block:

ts
import { ChatBox } from '@mui/x-chat';

Home and End jump to the first and latest message, and Page Up/Page Down scroll natively so long messages stay readable.

Key Action
Tab / Shift+Tab Enter or leave the message list (a single stop)
Arrow Up / Arrow Down Move focus to the previous / next message
Home / End Move focus to the first / latest message
Page Up / Page Down Native scrolling (kept unbound so a message taller than the viewport stays readable by keyboard)
Enter Drill into the focused message's controls (links, copy buttons, tool output, actions)
Escape Return from a message's controls to the message

Before the user interacts, the tab stop tracks the newest message. The tab stop is remembered per list, so leaving and re-entering the message list returns focus to the same message.

Interior controls and drill-in

Interactive content inside messages—links in Markdown, code-block copy buttons, tool and reasoning disclosures, source and file links—stays out of the tab order until the user drills into the focused message with Enter, and leaves it again on Escape. All controls remain mouse-clickable throughout. Message actions are additionally hidden (visibility: hidden) until the message is hovered or drilled into.

Custom interactive content rendered inside a message can participate in this model with the useMessageContentTabIndex() hook (or useMessageActionable() for full control), both exported from @mui/x-chat-headless:

function CustomControl() {
  const tabIndex = useMessageContentTabIndex();
  return (
    <button type="button" tabIndex={tabIndex}></button>
  );
}

Outside a roving message list both hooks leave the natural tab order untouched, so the same component works in standalone message compositions.

Set enableRovingFocus={false} on the message list to opt out entirely (for example when rendering fully custom rows that manage focus themselves).

Screen readers

  • The scroller element has role="log" and aria-live="polite", so newly arriving complete messages are announced.
  • A streaming message carries aria-busy="true" while it streams, hinting assistive technology to defer reading it until it completes.
  • A visually hidden role="status" region announces streaming transitions—"Assistant is responding" and "Response complete"—exactly once each, never per streamed token. The strings come from the locale text system (responseStreamingStartedAnnouncement, responseStreamingCompletedAnnouncement).
  • Each message is a role="article" labeled "Message from {author}".
  • Date dividers use role="separator".
  • The list aria-label is derived from the locale text system.

Slots

The following slots are available for customization through ChatBox:

Slot Component Description
messageList ChatMessageList The scrollable container
message ChatMessage Individual message row
avatar ChatMessageAvatar Author avatar
content ChatMessageContent Message bubble
meta ChatMessageMeta Timestamp and status
actions ChatMessageActions Hover action menu
group ChatMessageGroup Same-author message group
dateDivider ChatDateDivider Date separator

API

API

See the documentation below for a complete reference to all of the props and classes available to the components mentioned here.