Skip to content
+

Chat - Thread

Compose the active conversation pane with themed thread components and override individual slots.

The thread pane is the single-conversation view in a chat interface. It combines a header area, a scrollable message log, and a composer into one cohesive surface. MUI X Chat ships each region as a themed component built with styled() and Material UI theme tokens.

The following demo shows the thread in action:

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.

Component anatomy

ChatConversation                    ← thread shell, derives the active conversation
  ChatConversationHeader            ← header bar with divider styling
    ChatConversationTitle           ← conversation name
    ChatConversationSubtitle        ← secondary line (participants, presence, etc.)
    ChatConversationHeaderActions   ← action area (archive, mute, context menu)
  ChatMessageList                   ← virtualized scrollable message area
    ChatMessageGroup                ← groups consecutive same-author messages
      ChatMessage                   ← individual message row (grid layout)
        ChatMessageAvatar           ← author avatar
        ChatMessageContent          ← bubble + inner part renderers
        ChatMessageMeta             ← timestamp, status indicator
        ChatMessageActions          ← hover action menu
  ChatComposer             ← composer form (border-top)
    ChatComposerTextArea   ← auto-resizing textarea
    ChatComposerToolbar    ← button row
      ChatComposerAttachButton
      ChatComposerSendButton

All components are exported from @mui/x-chat.

Header anatomy

ChatConversationHeader is a <header> element with divider styling. It reads the active conversation through context so every child has access to the same conversation state without additional wiring.

import {
  ChatConversation,
  ChatConversationHeader,
  ChatConversationTitle,
  ChatConversationSubtitle,
  ChatConversationHeaderActions,
} from '@mui/x-chat';

ownerState and state flow

ChatConversation owns the conversation-level ownerState, and the header subcomponents inherit that same state through the slot system.

Conversation ownerState

Field Type Description
conversationId string | undefined Currently selected conversation ID
conversation ChatConversation | null Full active conversation object, when loaded
hasConversation boolean Whether the thread currently has a selection

Use hasConversation to hide action buttons or show a placeholder when no conversation is active. ChatConversationHeader, ChatConversationTitle, ChatConversationSubtitle, and ChatConversationHeaderActions all receive this same conversation-level state.

Overriding a header slot

The most targeted customization is to replace the element type on one slot while keeping everything else:

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.

Using ownerState in a custom title

The ownerState prop received by slot components carries the full conversation context. Use it to render dynamic content derived from the active conversation:

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

const LiveTitle = React.forwardRef(function LiveTitle(
  { ownerState, ...props },
  ref,
) {
  const memberCount = ownerState?.conversation?.metadata?.memberCount;
  return (
    <div ref={ref} {...props}>
      {ownerState?.conversation?.title ?? 'No conversation selected'}
      {memberCount != null && (
        <span style={{ fontWeight: 400, marginLeft: 8 }}>{memberCount} members</span>
      )}
    </div>
  );
});

function CustomConversationTitle(props) {
  return <ChatConversationTitle {...props} slots={{ root: LiveTitle }} />;
}

Message list anatomy

ChatMessageList renders three styled slots: a flex column outer shell, a scrolling scroller, and a padded content container.

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

ownerState and state flow

Slot key Default element ownerState fields
messageList div messageCount, isAtBottom
messageListScroller div same
messageListContent div same

Use isAtBottom to toggle a scroll-to-bottom affordance, and messageCount to show an empty-state illustration when the list has no messages.

Overriding the content container

Use slotProps when you only need to pass additional styling without swapping the element type:

<ChatMessageList
  renderItem={({ id, index }) => <ChatMessageGroup index={index} messageId={id} />}
  slotProps={{
    messageListContent: {
      sx: { gap: 1, paddingBlock: 2 },
    },
  }}
/>

Message groups and individual messages

ChatMessageGroup decides whether consecutive messages from the same author form a visual group, exposing isFirst and isLast grouping flags. ChatMessage then uses those flags to adjust spacing and avatar visibility.

Author identity for message rows is resolved from the message first, then enriched from members, currentUser, and active conversation participants using the author id. If your backend stores author fields elsewhere, use getMessageAuthorId, getMessageAuthorDisplayName, and getMessageAuthorAvatarUrl on the surrounding provider or ChatBox.

import {
  ChatMessageGroup,
  ChatMessage,
  ChatMessageAvatar,
  ChatMessageContent,
  ChatMessageMeta,
  ChatMessageActions,
} from '@mui/x-chat';

Message group ownerState

Field Type Description
isFirst boolean First message in a consecutive group
isLast boolean Last message in a consecutive group
authorRole 'user' | 'assistant' Role of the group author
authorId string | undefined Identity of the group author

Message ownerState

Field Type Description
role 'user' | 'assistant' Author role, drives bubble alignment
status string Delivery/streaming status
streaming boolean Whether the message is still streaming
error boolean Whether the message ended in an error state
isGrouped boolean Whether this row is part of a group
showAvatar boolean Controls the phantom-column width calculation

If no display name or avatar resolves for a message author, the built-in thread components skip those affordances instead of rendering role-based placeholders.

Overriding the message bubble

To customize the bubble, wrap ChatMessageContent and replace only its inner bubble slot. This preserves part iteration, markdown rendering, tool-call renderers, and source citations:

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.

Forward ref and spread ...props before your overrides so the wrapping pattern does not silently discard required props.

Adding custom children to a message row

When you need to insert additional content inside a message row—for example a copy-to-clipboard button—provide custom children to ChatMessage:

<ChatMessage messageId={id}>
  <ChatMessageAvatar />
  <ChatMessageContent />
  <CopyButton messageId={id} />
  <ChatMessageMeta />
</ChatMessage>

Children passed to ChatMessage are rendered inside the grid layout, so they inherit the same column structure as the default slots.

Composer anatomy

ChatComposer is a <form> element that handles text input, attachment state, and submission. Its children own specific regions of the composer.

import {
  ChatComposer,
  ChatComposerTextArea,
  ChatComposerToolbar,
  ChatComposerAttachButton,
  ChatComposerSendButton,
  ChatComposerHelperText,
} from '@mui/x-chat';

ownerState and state flow

Every composer component shares the same ownerState shape:

Field Type Description
isSubmitting boolean Form submission in progress
hasValue boolean Textarea has non-empty value
isStreaming boolean An assistant response is streaming
attachmentCount number Number of staged attachments
disabled boolean Input is disabled

ChatComposerHelperText also receives an error boolean.

Changing the placeholder and disabling the attach button

Use slotProps on ChatBox for light prop-only overrides that do not require a component replacement:

<ChatBox
  slotProps={{
    input: {
      placeholder: 'Ask a question…',
      'aria-label': 'Message input',
    },
    attach: {
      sx: { display: 'none' },
    },
  }}
/>

Overriding the send button with an ownerState-aware component

The isStreaming flag lets you replace the send icon with a stop icon while a response is in progress:

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.

Accessing message state in custom children

Custom children placed inside ChatMessage receive the message's ownerState through the slot system. When you need to read message state (role, streaming status, error) inside a deeply nested component, define a custom slot and accept ownerState as a prop:

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.

The ownerState includes role, status, streaming, error, isGrouped, and showAvatar—the same fields listed in the message ownerState table above.

For conversation-level state such as the active conversation title and participants, use the ownerState received by header slot components. See Header anatomy for details.

Full recomposition example

When ChatBox slots are not enough—for example when you want to add a pinned banner between the header and the message list, or position the typing indicator inside the header instead of above the composer—you can assemble the thread from individual Material UI components directly.

The following example shows a fully assembled thread pane without relying on ChatBox layout defaults. It inserts a custom warning banner between the header and the message list:

Material UI chat

Styled with your active MUI theme

Responses are AI-generated. Verify before acting.
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.

Wrap your custom thread with a ChatProvider from @mui/x-chat/headless to wire runtime state to your adapter.

See also

API

API

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