Skip to content
+

Chat - Headless indicators

Display streaming activity, typing presence, unread boundaries, and scroll-to-bottom affordances inside the message thread.

The demo below combines all three indicator primitives inside a chat thread:

Indicator thread
Typing, unread boundaries, and scroll state
Alice Chen
Alice Chen
Confirmed the unread marker still lands at the right boundary. Item 23.
You
You
Added one more note so the scroll affordance becomes visible while scrolled away. Item 24.
Sent
MUI Guide
MUI Guide
Reviewed the latest support transcript and normalized the key states. Item 25.
You
You
Queued the oldest messages for prepend pagination. Item 26.
Sent
Alice Chen
Alice Chen
Confirmed the unread marker still lands at the right boundary. Item 27.
You
You
Added one more note so the scroll affordance becomes visible while scrolled away. Item 28.
Sent
MUI Guide
MUI Guide
Reviewed the latest support transcript and normalized the key states. Item 29.
You
You
Queued the oldest messages for prepend pagination. Item 30.
Sent
Alice Chen
Alice Chen
Confirmed the unread marker still lands at the right boundary. Item 31.
You
You
Added one more note so the scroll affordance becomes visible while scrolled away. Item 32.
Sent
MUI Guide
MUI Guide
Reviewed the latest support transcript and normalized the key states. Item 33.
New messages
You
You
Queued the oldest messages for prepend pagination. Item 34.
Sent
Alice Chen
Alice Chen
Confirmed the unread marker still lands at the right boundary. Item 35.
You
You
Added one more note so the scroll affordance becomes visible while scrolled away. Item 36.
Sent

Indicator primitives

The indicator group is built from:

  • Indicators.StreamingIndicator.
  • Indicators.TypingIndicator.
  • Indicators.UnreadMarker.
  • Indicators.ScrollToBottomAffordance.

These primitives encode thread-specific behaviors — response generation, typing presence, unread state, and scroll position — that are easy to mishandle when rebuilt from scratch.

Showing response generation

StreamingIndicator renders while an assistant response is in flight. It covers two placements:

  • Trailing row — rendered after the last message row, it shows during the waiting phase (the request was sent but no assistant message exists yet). Pass index/items so it self-suppresses on every row except the last one.
  • Inside a message — rendered inside an assistant message (it reads the surrounding MessageContext, or an explicit message prop), it shows while that message has status: 'streaming'.

The mode prop mirrors the Material features.streamingIndicator flag: 'auto' (default) renders only in assistant-backed conversations, true always renders while a response is in flight, and false never renders. Reuse the gating in custom components with the useStreamingIndicatorVisibility(mode) hook.

Placing the indicator

<MessageList.Root
  renderItem={({ id, index }) => (
    <React.Fragment>
      <MessageGroup index={index} messageId={id} />
      <Indicators.StreamingIndicator index={index} items={items} />
    </React.Fragment>
  )}
/>

The root renders three bare <span> elements (the dots) for your CSS to animate, and is aria-hidden — the message list's status region already announces streaming transitions.

Showing typing activity

TypingIndicator reads typing state for the active conversation and resolves display names from:

  • Conversation participants.
  • Message authors already present in the thread.
  • Raw user ids when no richer user data is available.

It renders a polite live region and shows labels such as:

  • Alice is typing.
  • Alice, Bob are typing.

Placing the indicator

import { Indicators, Conversation } from '@mui/x-chat/headless';

<Conversation.Header>
  <Conversation.Title />
  <Conversation.Subtitle />
  <Indicators.TypingIndicator />
</Conversation.Header>;

Use it in the thread header or just above the composer when typing feedback should stay close to the active draft area.

Marking the unread boundary

UnreadMarker inserts a structural boundary into the message flow.

The unread boundary is derived from the active conversation state:

  • conversation.unreadCount.
  • conversation.readState.

The component renders only for the row that starts the unread region and applies role="separator" so the boundary stays meaningful to assistive technology.

Placing the marker

<MessageList.Root
  renderItem={({ id, index }) => (
    <React.Fragment>
      <Indicators.UnreadMarker index={index} messageId={id} />
      <MessageGroup index={index} messageId={id} />
    </React.Fragment>
  )}
/>

Replace the separator root and the label through slots.

Surfacing a scroll-to-bottom action

ScrollToBottomAffordance consumes message-list context and appears only when the user is away from the bottom of the thread.

It supports:

  • Scroll-to-bottom action wiring.
  • Unseen-message count badges.
  • An aria-label that includes the unseen count when present.

Render ScrollToBottomAffordance inside a MessageList.Root context — typically in a custom message-list root slot or another descendant of the list surface.

Placing the affordance

function MessageListShell(props) {
  return (
    <div {...props}>
      {props.children}
      <Indicators.ScrollToBottomAffordance />
    </div>
  );
}

When the user is already at the bottom of the thread, the affordance returns null.

Slots and owner state

The indicator primitives expose the following slot surfaces:

  • StreamingIndicator: root.
  • TypingIndicator: root.
  • UnreadMarker: root, label.
  • ScrollToBottomAffordance: root, badge.

Custom slots receive owner state such as:

  • The streaming phase ('waiting' or 'streaming').
  • Resolved typing users and count.
  • Unread-boundary presence and label.
  • Unseen-message count and isAtBottom.

Owner state makes it possible to map the indicators into an existing design system without rewriting the underlying behavior.

See also

  • See Message list for the list context that powers unread boundaries and scroll affordances.
  • See Thread for header composition patterns.
  • See Indicators in context for the runnable demo of these primitives.

API

API

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