Skip to content
+

Chat - Structure

Customize every part of the Chat UI by swapping slots, rearranging the layout, and translating user-facing strings.

Common customizations

The recipes below cover common customizations.

Ask me anything
Press Enter to start editing

Several of the recipes below applied at once.

Snippets assume an adapter is already created — see Adapters.

Layout and features

Hiding the attach button

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

Showing the conversation list

<ChatBox adapter={adapter} features={{ conversationList: true }} />

Hiding the scroll-to-bottom button

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

Composer

Changing the composer placeholder

<ChatBox
  adapter={adapter}
  localeText={{ composerInputPlaceholder: 'Ask anything...' }}
/>

Replacing the send button with a custom icon

import IconButton from '@mui/material/IconButton';
import SendRoundedIcon from '@mui/icons-material/SendRounded';

function MySendButton(props) {
  return (
    <IconButton {...props}>
      <SendRoundedIcon />
    </IconButton>
  );
}

<ChatBox adapter={adapter} slots={{ composerSendButton: MySendButton }} />;

Messages

Hiding avatars entirely

<ChatBox adapter={adapter} slots={{ messageAvatar: () => null }} />

Rendering tool calls as cards instead of JSON

// ToolCard is your own component.
<ChatBox
  adapter={adapter}
  partRenderers={{
    'tool-call': ({ part }) => (
      <ToolCard name={part.toolName} args={part.args} result={part.result} />
    ),
  }}
/>

Adding a "Regenerate" action on assistant messages

The function form of slotProps.messageActions receives the message context, so you can append declarative extraActions to specific rows — here, a "Regenerate" button on assistant replies. Each action's onClick receives (event, { message, chat }); call chat.regenerate(message.id) to drop the existing reply and request a fresh one through the runtime.

<ChatBox
  adapter={adapter}
  slotProps={{
    messageActions: ({ message }) =>
      message?.role === 'assistant'
        ? {
            extraActions: [
              {
                id: 'regenerate',
                label: 'Regenerate',
                onClick: (event, { chat }) => chat.regenerate(message.id),
              },
            ],
          }
        : {},
  }}
/>

Regeneration is allowed mid-thread: regenerating an earlier assistant message replaces only that reply and leaves later turns untouched. It works against any adapter — when the adapter does not implement regenerate, the runtime re-sends the anchoring user message instead.

Showing a custom empty state

import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import AutoAwesomeIcon from '@mui/icons-material/AutoAwesome';

<ChatBox
  adapter={adapter}
  slots={{
    emptyState: () => (
      <Stack alignItems="center" spacing={1} sx={{ p: 4 }}>
        <AutoAwesomeIcon />
        <Typography variant="h6">Ask me anything</Typography>
      </Stack>
    ),
  }}
/>;

Wrapping an existing slot instead of replacing it

The custom component receives the same props as the default. Render the original inside it:

import Box from '@mui/material/Box';
import { ChatMessageContent } from '@mui/x-chat';

function HighlightedMessage(props) {
  return (
    <Box sx={{ outline: props.message.flagged ? '2px solid red' : 'none' }}>
      <ChatMessageContent {...props} />
    </Box>
  );
}

<ChatBox adapter={adapter} slots={{ messageContent: HighlightedMessage }} />;

Localization

Translating the whole UI

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

<ChatBox
  adapter={adapter}
  localeText={{ ...chatEnUS, composerSendButtonLabel: 'Enviar' }}
/>;

See Localization for details.

Layout and structural props

A handful of ChatBox props shape the overall structure independently of slots:

Prop Values Purpose
variant 'default' | 'compact' Visual layout: full spacing with avatars, or Messenger-style
density 'compact' | 'standard' | 'comfortable' Vertical spacing between messages
layoutMode 'standard' | 'overlay' | 'split' Forces the responsive layout instead of deriving it from width
layoutModeBreakpoints Partial<ChatBoxLayoutModeBreakpoints> Container-width breakpoints used when layoutMode is omitted
<ChatBox variant="compact" density="comfortable" layoutMode="split" />

See Variants and density and Layout for the live demos.

Swapping slots

ChatBox is composed of named subcomponents (slots). Replace any of them with your own:

<ChatBox adapter={adapter} slots={{ messageContent: MyCustomMessageContent }} />

The component receives the same props as the default. This lets you wrap, extend, or fully replace any piece of the UI.

Passing extra props to slots

Pass additional props to any slot without replacing the component:

<ChatBox
  slotProps={{
    conversationList: { 'aria-label': 'Chat threads' },
    composerInput: { placeholder: 'Ask anything...' },
    composerSendButton: { sx: { borderRadius: 6 } },
  }}
/>

Available slots

Layout

Slot Default Description
root div Outermost container
layout div Arranges conversation + thread panes
conversationsPane div Conversations sidebar container
threadPane div Thread (message list + composer) container

Conversation

Slot Default Description
conversationRoot ChatConversation Thread wrapper element
conversationList ChatConversationList Conversation sidebar list
conversationHeader ChatConversationHeader Header bar above message list
conversationHeaderInfo ChatConversationHeaderInfo Title + subtitle group in the header
conversationTitle ChatConversationTitle Conversation name
conversationSubtitle ChatConversationSubtitle Secondary line
conversationHeaderActions ChatConversationHeaderActions Action buttons in header

Messages list

Slot Default Description
messageList ChatMessageList Scrollable message container
messageGroup ChatMessageGroup Same-author message group
dateDivider ChatDateDivider Date separator between groups (requires features.dateDivider)
unreadMarker ChatUnreadMarker "New messages" marker (requires features.unreadMarker)

Per message

Slot Default Description
messageRoot ChatMessage Individual message row
messageAvatar ChatMessageAvatar Author avatar (null to hide)
messageContent ChatMessageContent Message bubble
messageMeta ChatMessageMeta External timestamp (compact variant)
messageInlineMeta ChatMessageInlineMeta Inline timestamp (default variant)
messageError ChatMessageError Error card shown when status is error
messageActions ChatMessageActions Hover action bar (null to hide). The slotProps function form receives the message context; return extraActions to append declarative buttons
messageAuthorName styled div Author name label (null to hide)

Composer

Slot Default Description
composerRoot ChatComposer Composer container
composerInput ChatComposerTextArea Auto-resizing text area
composerSendButton ChatComposerSendButton Submit button
composerAttachButton ChatComposerAttachButton File attach trigger
composerAttachmentList ChatComposerAttachmentList Selected file pills
composerToolbar ChatComposerToolbar Button row
composerHelperText ChatComposerHelperText Disclaimer or hint

Indicators (top-level)

Slot Default Description
typingIndicator ChatTypingIndicator Typing dots
scrollToBottom ChatScrollToBottomAffordance Floating scroll button
suggestions ChatSuggestions Prompt suggestion chips
emptyState none Custom empty-thread view

Feature flags

Toggle built-in features on or off. When disabled, the corresponding slot is not rendered, even if you provide a custom component:

<ChatBox
  adapter={adapter}
  features={{
    conversationList: true, // show the conversation sidebar
    conversationHeader: true, // show the header bar above the message list
    dateDivider: true, // show date separators between calendar days
    unreadMarker: true, // show the "new messages" marker
    attachments: false, // hide attach button
    helperText: false, // hide helper text
    scrollToBottom: false, // hide scroll-to-bottom button
    suggestions: false, // hide suggestion chips
    autoScroll: { buffer: 300 }, // custom auto-scroll threshold
  }}
/>

conversationList, dateDivider, and unreadMarker are opt-in and default to false; the other flags default to true.

Note: the feature flag is helperText, while the corresponding slot is named composerHelperText.

Hiding a slot

Return null from a custom slot to remove it entirely:

<ChatBox slots={{ messageAvatar: () => null }} />

TypeScript types

Import the slot types for type-safe custom components:

import type { ChatBoxSlots, ChatBoxSlotProps } from '@mui/x-chat';
import { ChatMessageContent } from '@mui/x-chat';

const MyMessageContent: ChatBoxSlots['messageContent'] = (props) => (
  <Box className="custom-bubble">
    <ChatMessageContent {...props} />
  </Box>
);

Localization

Every user-facing string is customizable via the localeText prop:

<ChatBox
  adapter={adapter}
  localeText={{
    composerInputPlaceholder: 'Escribe un mensaje',
    composerSendButtonLabel: 'Enviar',
    composerAttachButtonLabel: 'Adjuntar archivo',
    scrollToBottomLabel: 'Ir al final',
    threadNoMessagesLabel: 'Sin mensajes aún',
  }}
/>

Full locale example (French)

<ChatBox
  adapter={adapter}
  localeText={{
    composerInputPlaceholder: 'Tapez un message',
    composerSendButtonLabel: 'Envoyer',
    composerAttachButtonLabel: 'Joindre un fichier',
    scrollToBottomLabel: 'Aller en bas',
    threadNoMessagesLabel: 'Aucun message',
    genericErrorLabel: 'Une erreur est survenue',
    loadingLabel: 'Chargement...',
    retryButtonLabel: 'Réessayer',
    unreadMarkerLabel: 'Nouveaux messages',
    typingIndicatorLabel: (users) => {
      const names = users.map((u) => u.displayName ?? u.id).join(', ');
      return users.length === 1 ? `${names} écrit...` : `${names} écrivent...`;
    },
  }}
/>

All locale keys

Composer

Key Default Description
composerInputPlaceholder "Type a message" Composer placeholder
composerInputAriaLabel "Message" Composer input accessible name
composerSendButtonLabel "Send message" Send button aria-label
composerAttachButtonLabel "Add attachment" Attach button aria-label
composerAttachInputLabel "Upload file" Hidden file input accessible name
composerAttachmentFallbackLabel "Attachment" Fallback name for unnamed attachment

Messages

Key Default Description
messageCopyButtonLabel "Copy" Copy message button
messageCopyCodeButtonLabel "Copy code" Copy code block button
messageCopiedCodeButtonLabel "Copied" Copied confirmation
messageEditedLabel "Edited" Edited message badge
messageDeletedLabel "Deleted" Deleted message placeholder
messageReasoningLabel "Reasoning" Reasoning part label
messageReasoningStreamingLabel "Thinking…" Reasoning streaming label
messageToolInputLabel "Tool called" Tool input section label
messageToolOutputLabel "Tool result" Tool output section label
messageToolApproveButtonLabel "Approve" Tool approval button
messageToolDenyButtonLabel "Deny" Tool denial button

Conversations

Key Default Description
conversationListNoConversationsLabel "No conversations" Empty conversation list
conversationListSearchPlaceholder "Search conversations" Conversation search placeholder
conversationHeaderMenuLabel "Open conversations" Header menu button
conversationHeaderBackLabel "Back to conversations" Header back button
conversationHeaderCloseLabel "Close conversations" Header close button
conversationHeaderNewChatLabel "New chat" Header new-chat button
conversationHeaderSettingsLabel "Settings" Header settings button

Status and indicators

Key Default Description
unreadMarkerLabel "New messages" Unread marker
retryButtonLabel "Retry" Retry button
reconnectButtonLabel "Reconnect" Reconnect button
scrollToBottomLabel "Scroll to bottom" Scroll affordance
threadNoMessagesLabel "No messages yet" Empty thread
threadNoMessagesHelperText "Type a message to get started" Empty-thread helper text
genericErrorLabel "Something went wrong" Generic error
loadingLabel "Loading…" Loading state
suggestionsLabel "Suggested prompts" Suggestions section

Accessibility and announcements

Key Default Description
messageListLabel "Message log" Message list accessible name
messageLabel "Message" Single message accessible name
messageActionsLabel "Message actions" Message actions container accessible name
messageAuthorUserLabel "User" Default author label for user role
messageAuthorAssistantLabel "Assistant" Default author label for assistant role
messageAuthorSystemLabel "System" Default author label for system role
conversationListLandmarkLabel "Conversations" Conversations sidebar landmark name
threadLandmarkLabel "Conversation" Active thread landmark name
composerLandmarkLabel "Message composer" Composer form landmark name
responseStreamingStartedAnnouncement "Assistant is responding" Announced when streaming starts
responseStreamingCompletedAnnouncement "Response complete" Announced when streaming completes

Function keys accept parameters:

Key Signature Default
messageStatusLabel (status) => string Maps status to label
toolStateLabel (state) => string Maps tool state to label
messageTimestampLabel (dateTime) => string Formats to HH:MM
conversationTimestampLabel (dateTime) => string Relative day or HH:MM
typingIndicatorLabel (users) => string "Alice is typing"
scrollToBottomWithCountLabel (count) => string "Scroll to bottom, N new messages"
composerRemoveAttachmentLabel (fileName) => string "Remove {fileName}"

API

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