Skip to content
+

Chat - Headless composer

Compose chat messages from headless primitives that handle submission, IME-safe Enter, attachments, and helper text.

Attachments composer
Draft text, helper copy, and upload state
MUI Guide
MUI Guide
Add files, write a note, and send the draft to see how the composer clears after a successful response.
Press Enter to send. Shift+Enter keeps a new line, and the file trigger stays connected to the hidden input.

Composer primitives

The composer surface is built from:

  • Composer.Root
  • Composer.TextArea
  • Composer.Toolbar
  • Composer.AttachButton
  • Composer.HelperText
  • Composer.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, and attachmentCount

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
  • Enter to submit
  • Shift+Enter for 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
  • Shift is not pressed
  • the native event is not composing
  • no earlier onKeyDown handler 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:

  • hasValue
  • isSubmitting
  • isStreaming
  • attachmentCount

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

API

API

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