Chat - Headless composer
Compose chat messages from headless primitives that handle submission, IME-safe Enter, attachments, and helper text.
Composer primitives
The composer surface is built from:
Composer.RootComposer.TextAreaComposer.ToolbarComposer.AttachButtonComposer.HelperTextComposer.SendButton
Composing the primitives
import { Composer } from '@mui/x-chat/headless';
function ThreadComposer() {
return (
<Composer.Root>
<Composer.TextArea placeholder="Write a message" />
<Composer.HelperText />
<Composer.Toolbar>
<Composer.AttachButton />
<Composer.SendButton />
</Composer.Toolbar>
</Composer.Root>
);
}
This pattern gives you a working composer skeleton while leaving every visual decision to you.
Wrapping the composer form
Composer.Root is a structural form wrapper around the headless composer state.
It owns:
- submit-on-form-submit wiring
- composer context for the child primitives
- owner state such as
hasValue,isSubmitting,isStreaming, andattachmentCount
Use it to style global draft states such as empty, busy, or attachment-heavy composers.
Rendering the text input
Composer.TextArea handles the runtime behaviors that make chat inputs hard to implement from scratch.
It supports:
- binding to the current composer value
- automatic textarea resizing as the draft grows
Enterto submitShift+Enterfor a new line- composition tracking for IME input
- focus restoration when the active conversation changes and the previous input unmounts
Handling IME-safe Enter
The input only submits when all of these are true:
- the key is
Enter Shiftis not pressed- the native event is not composing
- no earlier
onKeyDownhandler prevented the default behavior
East Asian IME flows stay intact without extra app-level bookkeeping.
Configuring the text input
<Composer.TextArea aria-label="Message" minRows={1} placeholder="Reply in thread" />
When replacing the root slot, keep the textarea-like behavior unless you intend to build a different draft surface.
Attaching files
Composer.AttachButton pairs a visible trigger with a hidden file input.
By default it:
- opens the hidden input on click
- accepts multiple files
- adds each selected file into the composer attachment collection
- resets the file input value after selection so the same file can be picked again
The primitive exposes both root and input slots, which is useful when you want a custom trigger element or need to style the hidden input for a testing harness.
Configuring the attach button
<Composer.AttachButton
aria-label="Add files"
slotProps={{
input: {
accept: 'image/*,.pdf',
},
}}
/>
Surfacing helper text and errors
Composer.HelperText is the default place for draft-level status and error messaging.
It renders:
- explicit children when you provide them
- otherwise the current runtime error message from the composer context
That makes it a good structural slot for validation copy, transport errors, and retry guidance.
<Composer.HelperText>
Files are uploaded after the message is sent.
</Composer.HelperText>
If you omit children, the component falls back to the active runtime error text and returns null when there is nothing to show.
Submitting the draft
Composer.SendButton is a submit button wired to composer state.
It disables itself when:
- the draft is empty
- a stream is already active
- the button is disabled externally
The default button type is submit, so it works inside Composer.Root without extra wiring.
Slots and owner state
Composer primitives expose slots and slotProps throughout the surface.
Custom slots receive owner state derived from the composer context, including:
hasValueisSubmittingisStreamingattachmentCount
Use these values for styling patterns such as:
- hiding the send button until a value exists
- emphasizing the attach trigger when attachments are present
- dimming the toolbar while a stream is active
See also
- See Indicators for typing, unread, and scroll affordances around the composer.
- See Customization for slot and owner-state patterns across the headless surface.
- See Composer with attachments for the runnable demo.
API
API
See the documentation below for a complete reference to all of the props and classes available to the components mentioned here.