Hello! I am styled using your active Material UI theme. Try sending a message.
Chat - Look and feel
Change colors, layout style, density, and typography—from quick props to full brand theming.
Live playground
The same ChatBox re-themes itself when you swap the layout variant, density, primary color, or border radius—every change comes from a single Material UI theme and two top-level props (variant and density):
Choosing a layout style
The variant prop controls how messages and the composer are arranged:
// Bubbles—colored message bubbles, right-aligned for user (WhatsApp, Telegram)
<ChatBox adapter={adapter} variant="default" />
// Flat—no bubbles, stacked messages with inline sender info (Slack, ChatGPT, Claude)
<ChatBox adapter={adapter} variant="compact" />
The same conversation rendered with each variant—default uses colored bubbles with own messages on the trailing edge; compact drops the bubble and shows the author inline:
'use client';
import * as React from 'react';
import Box from '@mui/material/Box';
import Paper from '@mui/material/Paper';
import Typography from '@mui/material/Typography';
import { ChatBox } from '@mui/x-chat';
import type { ChatVariant } from '@mui/x-chat';
import { createEchoAdapter } from 'docs/data/chat/core/examples/shared/demoUtils';
import {
demoUsers,
minimalConversation,
minimalMessages,
} from 'docs/data/chat/core/examples/shared/demoData';
const adapter = createEchoAdapter({ respond: () => 'echo' });
interface VariantPanelProps {
variant: ChatVariant;
caption: string;
}
function VariantPanel({ variant, caption }: VariantPanelProps) {
return (
<Paper
variant="outlined"
sx={{
p: 2,
borderRadius: 2,
display: 'flex',
flexDirection: 'column',
gap: 1.5,
backgroundColor: 'background.default',
}}
>
<Typography variant="caption" color="text.secondary">
{caption}
</Typography>
<Box sx={{ height: 360 }}>
<ChatBox
adapter={adapter}
currentUser={demoUsers.you}
members={[demoUsers.you, demoUsers.agent]}
initialConversations={[minimalConversation]}
initialMessages={minimalMessages}
initialActiveConversationId={minimalConversation.id}
variant={variant}
sx={{ height: '100%' }}
/>
</Box>
</Paper>
);
}
export default function MessageVariantsDemo() {
return (
<Box
sx={{
display: 'grid',
gap: 2,
gridTemplateColumns: { xs: '1fr', md: 'repeat(2, minmax(0, 1fr))' },
width: '100%',
}}
>
<VariantPanel variant="default" caption='variant="default"' />
<VariantPanel variant="compact" caption='variant="compact"' />
</Box>
);
}
The demo below shows how the same variant prop drives the composer's two stock layouts:
variant="default"
Stacked layout — attachment list, textarea, then toolbar with attach + send.variant="compact"
Inline layout — attach button, textarea and send button share a single row.Choosing a density
The density prop controls vertical spacing between messages:
<ChatBox adapter={adapter} density="compact" /> // Tight spacing
<ChatBox adapter={adapter} density="standard" /> // Default
<ChatBox adapter={adapter} density="comfortable" /> // Spacious
standard is the default when the prop is omitted.
'use client';
import * as React from 'react';
import Box from '@mui/material/Box';
import Paper from '@mui/material/Paper';
import Typography from '@mui/material/Typography';
import { ChatBox } from '@mui/x-chat';
import type { ChatDensity } from '@mui/x-chat';
import { createEchoAdapter } from 'docs/data/chat/core/examples/shared/demoUtils';
import {
demoUsers,
minimalConversation,
minimalMessages,
} from 'docs/data/chat/core/examples/shared/demoData';
const adapter = createEchoAdapter({ respond: () => 'echo' });
interface DensityPanelProps {
density: ChatDensity;
caption: string;
}
function DensityPanel({ density, caption }: DensityPanelProps) {
return (
<Paper
variant="outlined"
sx={{
p: 2,
borderRadius: 2,
display: 'flex',
flexDirection: 'column',
gap: 1.5,
backgroundColor: 'background.default',
}}
>
<Typography variant="caption" color="text.secondary">
{caption}
</Typography>
<Box sx={{ height: 360 }}>
<ChatBox
adapter={adapter}
currentUser={demoUsers.you}
members={[demoUsers.you, demoUsers.agent]}
initialConversations={[minimalConversation]}
initialMessages={minimalMessages}
initialActiveConversationId={minimalConversation.id}
density={density}
sx={{ height: '100%' }}
/>
</Box>
</Paper>
);
}
export default function DensityComparisonDemo() {
return (
<Box
sx={{
display: 'grid',
gap: 2,
gridTemplateColumns: { xs: '1fr', md: 'repeat(3, minmax(0, 1fr))' },
width: '100%',
}}
>
<DensityPanel density="compact" caption='density="compact"' />
<DensityPanel density="standard" caption='density="standard" (default)' />
<DensityPanel density="comfortable" caption='density="comfortable"' />
</Box>
);
}
Message states
ChatMessage adapts to the role and status it reads from the store—no extra props required:
Default assistant bubble — uses palette.grey[100] (light) / palette.grey[800] (dark).
Outgoing user bubble — uses palette.primary.main.
Streaming bubble — animates while the assistant is typing…
This message failed to send.
Applying one-off styles with sx
Apply one-off styles directly on ChatBox:
<ChatBox
adapter={adapter}
sx={{
height: 500,
width: '100%',
borderRadius: 2,
border: '1px solid',
borderColor: 'divider',
}}
/>
Theme component overrides
For global style rules, use createTheme with component keys—chat text inherits theme.typography, so a fontFamily swap re-fonts the whole surface.
Import '@mui/x-chat/themeAugmentation' for TypeScript autocomplete:
import type {} from '@mui/x-chat/themeAugmentation';
const theme = createTheme({
typography: { fontFamily: '"Inter", sans-serif' },
components: {
MuiChatBox: {
styleOverrides: {
root: ({ theme }) => ({
border: '1px solid',
borderColor: theme.palette.divider,
}),
},
},
MuiChatMessage: {
styleOverrides: {
bubble: ({ theme: t }) => ({
borderRadius: t.shape.borderRadius * 3,
}),
},
},
},
});
For dark mode, CSS class injection, and the full override-key list, see Material UI customization.
Styling internal slots
Style any internal subcomponent without replacing it:
<ChatBox
slotProps={{
composerInput: { placeholder: 'Ask anything...' },
composerSendButton: { sx: { borderRadius: 6 } },
}}
/>
Both techniques applied to a live ChatBox—the theme override rounds every bubble, and slotProps restyles the send button and placeholder:
To replace subcomponents entirely, see Slot overrides.
CSS class names
Inject custom class names or use the built-in constants for CSS targeting:
import { chatBoxClasses } from '@mui/x-chat';
// Class constants: chatBoxClasses.root, chatBoxClasses.layout,
// chatBoxClasses.conversationsPane, chatBoxClasses.threadPane
<ChatBox classes={{ root: 'my-chat-root', layout: 'my-chat-layout' }} />;
Tailwind CSS
Prefer to bring your own CSS instead of the Material UI theme? Use the headless primitives: see Headless components and Tailwind CSS.
The headless primitives from @mui/x-chat/headless render semantic HTML with zero built-in styles and data-* attributes for state—a natural fit for Tailwind:
import { Chat, Composer, Message, MessageList } from '@mui/x-chat/headless';
<Chat.Root adapter={adapter}>
<Chat.Layout>
<MessageList.Root renderItem={...} />
<Composer.Root className="flex flex-col gap-1 border-t border-gray-200 p-3">
<Composer.TextArea
className="resize-none rounded-lg border border-gray-300 px-3 py-2 text-sm
focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<Composer.Toolbar className="flex items-center justify-end gap-2">
<Composer.SendButton
className="rounded-lg bg-blue-600 px-3 py-1.5 text-sm text-white
hover:bg-blue-700 disabled:opacity-40"
/>
</Composer.Toolbar>
</Composer.Root>
</Chat.Layout>
</Chat.Root>
Use data-* attributes for state-based styling without JavaScript:
{
/* data-role="user" or data-role="assistant" */
}
<Message.Root className="flex gap-3 data-[role=user]:flex-row-reverse" />;
{
/* The button gets the native disabled attribute while the input is empty; data-disabled reflects the composer's disabled prop */
}
<Composer.SendButton className="bg-blue-600 disabled:opacity-40" />;
These attributes are rendered by the headless primitives; the Material components consume the same state internally but expose it through classes instead.
| Component | Attribute | Values |
|---|---|---|
Message.Root |
data-role |
"user", "assistant" |
Message.Root |
data-status |
"pending", "sending", "streaming", "sent", "read", "error", "cancelled" |
Composer.SendButton |
data-disabled |
present when the composer is disabled via props |
Composer.SendButton |
data-has-value |
present when the input is non-empty |
Composer.Root |
data-is-streaming |
present / absent |
ConversationList.Item |
data-selected |
present / absent |
API
See the documentation below for a complete reference to all of the props and classes available to the components mentioned here.