Skip to content
+

Chat - Scrolling

Keep new messages visible with auto-scroll, scroll-to-bottom affordance, and automatic history loading on scroll.

Interactive playground

Try the scroll-to-bottom affordance live: scroll up inside the preview to reveal the jump-to-latest button, and try each scrollBehavior.

ChatScrollToBottomAffordance
Floating jump-to-latest button — appears once the user scrolls away from the bottom.

MUI Assistant
MUI Assistant

Message 1 of 20

You

Message 2 of 20

MUI Assistant
MUI Assistant

Message 3 of 20

You

Message 4 of 20

MUI Assistant
MUI Assistant

Message 5 of 20

You

Message 6 of 20

MUI Assistant
MUI Assistant

Message 7 of 20

You

Message 8 of 20

MUI Assistant
MUI Assistant

Message 9 of 20

You

Message 10 of 20

MUI Assistant
MUI Assistant

Message 11 of 20

You

Message 12 of 20

MUI Assistant
MUI Assistant

Message 13 of 20

You

Message 14 of 20

MUI Assistant
MUI Assistant

Message 15 of 20

You

Message 16 of 20

MUI Assistant
MUI Assistant

Message 17 of 20

You

Message 18 of 20

MUI Assistant
MUI Assistant

Message 19 of 20

You

Message 20 of 20

props
scrollBehaviorenum · 3
ScrollIntoView behavior used when the button is clicked.
fixture data
message count20

The message list automatically manages scroll position so new messages and streaming content stay visible without user intervention. Users can still scroll up to read earlier messages without losing their place.

Auto-scrolling

The message list automatically scrolls to the bottom when:

  • The user sends a new message (always active, regardless of configuration).
  • 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. The same buffer defines the bottom zone for the message list's onReachBottom callback, which fires once each time the viewport enters it.

Configuration

Control auto-scrolling through the features prop on ChatBox. The default buffer is 150px:

// Default — auto-scroll on with a 150px buffer
<ChatBox adapter={adapter} />

// Custom 300px buffer threshold
<ChatBox adapter={adapter} features={{ autoScroll: { buffer: 300 } }} />

// Disable auto-scroll entirely
<ChatBox adapter={adapter} features={{ autoScroll: false }} />

When auto-scroll is disabled, users can still scroll to the bottom manually using the scroll-to-bottom affordance.

Send a message, then scroll up while the long reply streams in — auto-scroll pauses once you're more than buffer pixels from the bottom and resumes when you scroll back within it.

buffer (px)
Material UI chat

Styled with your active MUI theme

You
You

Question 1: how does the buffer threshold work?

MUI Assistant
MUI Assistant

Answer 1: auto-scroll keeps following until you scroll past it.

You
You

Question 2: how does the buffer threshold work?

MUI Assistant
MUI Assistant

Answer 2: auto-scroll keeps following until you scroll past it.

You
You

Question 3: how does the buffer threshold work?

MUI Assistant
MUI Assistant

Answer 3: auto-scroll keeps following until you scroll past it.

You
You

Question 4: how does the buffer threshold work?

MUI Assistant
MUI Assistant

Answer 4: auto-scroll keeps following until you scroll past it.

You
You

Question 5: how does the buffer threshold work?

MUI Assistant
MUI Assistant

Answer 5: auto-scroll keeps following until you scroll past it.

You
You

Question 6: how does the buffer threshold work?

MUI Assistant
MUI Assistant

Answer 6: auto-scroll keeps following until you scroll past it.

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. Try it in the interactive playground at the top of this page.

The affordance is enabled by default. Disable it with:

<ChatBox adapter={adapter} features={{ scrollToBottom: false }} />

The affordance also supports an unseen-message count badge and an aria-label that includes the unseen count when present.

The affordance's accessible name comes from the scrollToBottomLabel and scrollToBottomWithCountLabel locale strings — see Localization to translate them. The message list also renders a visually hidden role="status" live region (the messageListStatus slot) that announces when a streamed response starts and completes to screen readers. For the full keyboard and screen-reader model, see the Accessibility page and Message list—Accessibility.

Scrolling programmatically with a ref

The ChatMessageList component exposes a ref handle for programmatic 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 from child components

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

Loading message history

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.

The adapter signals whether more history is available through the hasMore flag:

async listMessages({ conversationId, cursor }) {
  const res = await fetch(`/api/conversations/${conversationId}/messages?cursor=${cursor ?? ''}`);
  const { messages, nextCursor, hasMore } = await res.json();
  return { messages, cursor: nextCursor, hasMore };
},

When hasMore is true, the message list continues to load older messages as the user scrolls up. When hasMore is false, no more history is requested when the user reaches the top.

You can also check the history loading state programmatically:

const { hasMoreHistory, loadMoreHistory } = useChat();

// Trigger manually if needed
await loadMoreHistory();

See also

  • Message list for details on date dividers, grouping, and density.
  • Streaming for details on how auto-scroll follows streaming content.
  • Adapter for details on the listMessages method that powers history loading.

API

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