# Message composer UI

> **Info:** This is release candidate documentation for **Stream Chat React Native SDK v10**. For the latest stable version, see the [latest version (v9)](https://getstream.io/chat/docs/sdk/react-native/).

The message composer UI is where a user writes, edits and sends a message. The message being written lives in a `MessageComposer` object from `stream-chat`: its text, attachments, quoted message and poll. The UI renders that object and calls it when the user acts. This page covers which composer the UI drives, the parts of the UI, the attachment picker and voice recording, and what each part reads. The props are in the [MessageComposer reference](https://getstream.io/chat/docs/sdk/react-native/v10/component-reference/message-composer/messagecomposer/), and the object is on [Message composer](https://getstream.io/chat/docs/sdk/react-native/v10/state/writing-messages/message-composer/).

## Which composer it drives

`<MessageComposer />` and every part inside it work on the composer that `useMessageComposer()` returns, the first of:

1. the composer of the message being edited, while the user edits one;
2. the thread's composer, `thread.messageComposer`, inside a `Thread`;
3. the channel's composer, `channel.messageComposer`.

`Channel` and `Thread` provide it, so `MessageComposer` works only inside one of them.

Editing uses a composer of its own. When the user picks "Edit" on a message, the SDK loads the message into a composer for that message, and the composer UI switches to it. What the user was writing stays in the channel's composer. The edited-message preview shows above the input, and sending saves the edit. Sending, or closing the preview, switches back to the channel's or thread's composer. Editing a message that bounced sends it again as a new message.

Sending calls `MessageComposer.send()`, or `MessageComposer.update()` for an edit. When the user can't send links in the channel and the text contains one, an alert says so and nothing is sent. When the composer has nothing to send, the text stays in the input.

The composer's content stays in the composer object when the screen unmounts, so the channel list row shows it as a draft until it's sent or cleared. `MessageComposer` doesn't save drafts to the server; with drafts turned on, call `MessageComposer.createDraft()` when the user leaves the screen. With drafts on, `MessageComposer` inside a `Thread` loads the thread's saved draft when it mounts. [Drafts](https://getstream.io/chat/docs/sdk/react-native/v10/features/messaging/drafts/) covers them.

## Layout

`MessageComposer` renders a row, from start to end:

1. **The leading view**, `MessageComposerLeadingView`: the attach button, which opens the [attachment picker](#the-attachment-picker). It's hidden while a command is active, when the user lacks the `upload-file` capability, and when no picker is available.
2. **The input box**, which holds:
- **the header view**, `MessageInputHeaderView`, with the [previews](#previews);
- **the input view**, `InputView`: the command chip, the text input, and in a thread the also-send-to-channel checkbox;
- **the trailing view**, `MessageInputTrailingView`, with the [send button or its replacement](#the-send-button-and-its-replacements).
3. **The trailing view after the box**, `MessageComposerTrailingView`, which renders nothing by default.

By default the composer sits below the message list, with a border on top. With `messageInputFloating` on `Channel` or `Thread`, it floats over the list instead, and the list leaves room for it.

### Previews

The header view shows what the message will carry. Each preview renders only when the composer has something for it:

| Preview        | Shows                                                                                      | Override key                  |
| -------------- | ------------------------------------------------------------------------------------------ | ----------------------------- |
| Edited message | The message being edited, with a close button to cancel. Takes the quoted message's place. | `Reply`                       |
| Quoted message | The message being replied to, with a close button to remove it                             | `Reply`                       |
| Attachments    | Images, videos, files and recordings, with their upload progress and a remove button       | `AttachmentUploadPreviewList` |
| Link previews  | A card for each link in the text                                                           | None                          |

`Reply` is the same component that shows a quoted message inside a message bubble. It gets `mode="edit"` or `mode="reply"` in the composer.

`AttachmentUploadPreviewList` renders one preview per attachment through `ImageAttachmentUploadPreview`, `VideoAttachmentUploadPreview`, `FileAttachmentUploadPreview` and `AudioAttachmentUploadPreview`, and their upload states through `ImageUploadInProgressIndicator`, `ImageUploadRetryIndicator`, `ImageUploadNotSupportedIndicator` and the `File...` versions of the same three. Each of these is an override key.

### The input

The text input reads and writes `textComposer`: the text, the cursor position, the mentioned users and the active command. `additionalTextInputProps` on `Channel` or `Thread` passes props to the `TextInput`, and `setInputRef` gives you its ref.

Typing a trigger opens the suggestion list, `AutoCompleteSuggestionList`, above the composer:

- `@` suggests people to mention, and, where the channel allows them, `@channel`, `@here`, roles and user groups. Each row is a `MentionSuggestionItem`.
- `/` suggests the channel's commands.
- `:` suggests emoji, once you add the emoji middleware, `createTextComposerEmojiMiddleware`, to the text composer. See [Emoji](https://getstream.io/chat/docs/sdk/react-native/v10/features/rich-content/emoji/).

The suggestions come from the text composer's middleware; [Suggestions](https://getstream.io/chat/docs/sdk/react-native/v10/state/writing-messages/suggestions/) explains them. The list renders inside the message list, so a screen without a `MessageList` or `MessageFlashList` shows none.

Picking a command, such as `/giphy`, shows it as a chip before the input with a close button that clears it. The attach button hides while a command is active.

### The send button and its replacements

The trailing view shows one control, the first of these that applies:

1. **The stop button**, `StopMessageStreamingButton`, while an AI is thinking about or generating a reply in the channel. It calls `Channel.stopAIResponse()`. See [SDK integration](https://getstream.io/chat/docs/sdk/react-native/v10/features/ai/sdk-integration/).
2. **A checkmark button**, while the user edits a message or has a command active. It saves the edit or runs the command, and it's disabled while the composer is empty.
3. **The cooldown timer**, `CooldownTimer`, while slow mode stops the user from sending. It shows the seconds left. See [Slow mode](https://getstream.io/chat/docs/sdk/react-native/v10/features/channels/slow-mode/).
4. **The record button**, `StartAudioRecordingButton`, when voice recording is on and the composer has no text and no attachments. See [Voice recording](#voice-recording).
5. **The send button**, `SendButton`, otherwise. It's disabled until the composer has something to send.

### In threads

In a thread, a checkbox under the input, "Also send to channel", also posts the reply to the channel. It toggles `showReplyInChannel` in the composer's state. `allowThreadMessagesInChannel={false}` on `Thread` hides it.

### When the user can't send

When the user lacks the `send-message` capability in the channel, for example in a frozen channel, `MessageComposer` renders `SendMessageDisallowedIndicator` instead, "You can't send messages in this channel". It waits until the channel's capabilities are known, so it doesn't flash while the channel loads, and it keeps the composer while the user edits a message.

## The attachment picker

The attachment picker is a bottom sheet that `Channel` and `Thread` render under the composer. The attach button opens it and closes the keyboard. Opening the keyboard closes it, and so does the Android back button.

At its top, a selection bar, `AttachmentPickerSelectionBar`, has a button for each source:

| Button   | Shows when                                                                   | Opens                                    |
| -------- | ---------------------------------------------------------------------------- | ---------------------------------------- |
| Photos   | `hasImagePicker`, true when an image picker or media library is installed    | A grid of the device's photos and videos |
| Camera   | `hasCameraPicker`, true when an image picker is installed                    | The camera                               |
| Files    | `hasFilePicker`, true when a document picker is installed                    | The system file picker                   |
| Poll     | Polls are on for the channel, the user can send polls, and it isn't a thread | The poll creation dialog                 |
| Commands | `hasCommands`, true when the channel has commands                            | The list of commands                     |

Below the bar, `AttachmentPickerContent` shows the selected source. Picked photos, videos and files are uploaded into the composer as they're picked, and show in its previews. `compressImageQuality` sets how much images are compressed before upload.

Without a media library module, `disableAttachmentPicker` is `true` by default: the sheet shows the bar only, and each button opens the system picker directly. Three more props change the picker: `attachmentPickerBottomSheetHeight` sets its height, `shouldRenderAttachmentPicker={false}` leaves it out, and `handleAttachButtonPress` replaces what the attach button does. Which native modules provide each source is on [Installation](https://getstream.io/chat/docs/sdk/react-native/v10/getting-started/installation/), and the picker's props are in the [Attachment picker reference](https://getstream.io/chat/docs/sdk/react-native/v10/component-reference/message-composer/attachment-picker/).

## Voice recording

Voice recording is off by default. Turn it on with `audioRecordingEnabled` on `Channel` and `Thread`; it needs an audio recording module installed. With it on and the composer empty, the record button replaces the send button:

1. The user holds the record button for at least `asyncMessagesMinimumPressDuration` (500 ms by default) to start recording. A short tap shows "Hold to record. Release to save." instead. Without the microphone permission, a notification asks the user to allow it in the settings.
2. While the user holds it, `AudioRecorder` replaces the input with the recording time and "Slide to Cancel". Sliding left by `asyncMessagesSlideToCancelDistance` cancels the recording. Sliding up by `asyncMessagesLockDistance` locks it, so the user can let go.
3. A locked recording shows `AudioRecordingInProgress`, with its waveform, and `AudioRecordingLockIndicator`. Stopping it shows `AudioRecordingPreview`, where the user can play the recording before sending or deleting it.
4. Releasing the button, or sending from the recorder, adds the recording to the composer as a voice recording attachment. With `audioRecordingSendOnComplete`, it sends the message at once.

[Voice recordings and audio playback](https://getstream.io/chat/docs/sdk/react-native/v10/features/rich-content/voice-recordings-and-audio-playback/) covers the feature, and the [Audio recorder reference](https://getstream.io/chat/docs/sdk/react-native/v10/component-reference/message-composer/audio-recorder/) its parts.

## What each part reads

Each part subscribes to the store of the composer piece it shows, and calls that piece when the user acts:

| Composer piece        | Used by                                                                                |
| --------------------- | -------------------------------------------------------------------------------------- |
| `state`               | The quoted and edited message previews, the also-send-to-channel checkbox              |
| `textComposer`        | The input, the suggestion list, the command chip, the send button and its replacements |
| `attachmentManager`   | The attachment previews, the attachment picker, voice recording, the send button       |
| `linkPreviewsManager` | The link previews                                                                      |
| `pollComposer`        | The poll creation dialog                                                               |

The composer UI has no part for `locationComposer` or `customDataManager`. A part of your own reads them the same way as the others.

A part of your own gets the composer with `useMessageComposer()` and subscribes to a store with `useStateStore`. This one counts the characters typed, after the input box, in the slot that is empty by default:

```tsx title="src/ComposerWithCount.tsx"
import { StyleSheet, Text } from "react-native";
import type { TextComposerState } from "stream-chat";
import {
  MessageComposer,
  useMessageComposer,
  useStateStore,
  WithComponents,
} from "stream-chat-react-native";

const textSelector = ({ text }: TextComposerState) => ({ text });

const CharacterCount = () => {
  const { textComposer } = useMessageComposer();
  const { text } = useStateStore(textComposer.state, textSelector);

  return <Text style={styles.count}>{text.length}</Text>;
};

const overrides = { MessageComposerTrailingView: CharacterCount };

export const ComposerWithCount = () => (
  <WithComponents overrides={overrides}>
    <MessageComposer />
  </WithComponents>
);

const styles = StyleSheet.create({
  count: { alignSelf: "flex-end", padding: 8 },
});
```

The same part works in a channel, in a thread and while editing, because the hook returns the composer of the place it's rendered in. `ComposerWithCount` still has to render inside a `Channel` or `Thread`.

## Replacing parts

Register replacements with `WithComponents`. [Overriding components](https://getstream.io/chat/docs/sdk/react-native/v10/ui/customizing/overriding-components/) explains how overrides apply.

| Override key                                                                                                                  | Replaces                                                                                                         |
| ----------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `Input`                                                                                                                       | No default. When set, it replaces the whole row of the composer. Gets `additionalTextInputProps` and `getUsers`. |
| `MessageComposerLeadingView`                                                                                                  | The area before the input box, with the attach button                                                            |
| `AttachButton`                                                                                                                | The attach button                                                                                                |
| `MessageInputHeaderView`                                                                                                      | The previews above the input                                                                                     |
| `Reply`                                                                                                                       | The quoted and edited message previews, and quoted messages in message bubbles                                   |
| `AttachmentUploadPreviewList`                                                                                                 | The attachment previews                                                                                          |
| `InputView`                                                                                                                   | The command chip, the text input and the also-send-to-channel checkbox                                           |
| `AutoCompleteSuggestionList`                                                                                                  | The suggestion list                                                                                              |
| `AutoCompleteSuggestionHeader`, `AutoCompleteSuggestionItem`                                                                  | The suggestion list's header, and each suggestion                                                                |
| `MentionSuggestionItem`                                                                                                       | Each `@` suggestion                                                                                              |
| `MessageInputTrailingView`                                                                                                    | The area with the send button and its replacements                                                               |
| `SendButton`                                                                                                                  | The send button                                                                                                  |
| `CooldownTimer`                                                                                                               | The cooldown timer                                                                                               |
| `StopMessageStreamingButton`                                                                                                  | The stop button                                                                                                  |
| `StartAudioRecordingButton`                                                                                                   | The record button                                                                                                |
| `AudioRecorder`, `AudioRecordingInProgress`, `AudioRecordingLockIndicator`, `AudioRecordingPreview`, `AudioRecordingWaveform` | The parts of voice recording                                                                                     |
| `MessageComposerTrailingView`                                                                                                 | Nothing by default. Rendered after the input box.                                                                |
| `SendMessageDisallowedIndicator`                                                                                              | What renders when the user can't send                                                                            |
| `AttachmentPickerSelectionBar`, `AttachmentPickerContent`                                                                     | The attachment picker's bar and content                                                                          |
| `ImageOverlaySelectedComponent`                                                                                               | The mark on a photo picked in the picker's grid                                                                  |

The command chip and the also-send-to-channel checkbox have no override key of their own: replace `InputView` to change them.

The [Custom composer UI cookbook](https://getstream.io/chat/docs/sdk/react-native/v10/ui-cookbook/composer/custom-composer/) builds a composer UI of its own.

## Used in

- [Sending, editing and deleting](https://getstream.io/chat/docs/sdk/react-native/v10/features/messaging/sending-editing-and-deleting/)
- [Drafts](https://getstream.io/chat/docs/sdk/react-native/v10/features/messaging/drafts/)
- [Attachments and uploads](https://getstream.io/chat/docs/sdk/react-native/v10/features/rich-content/attachments-and-uploads/)
- [Voice recordings and audio playback](https://getstream.io/chat/docs/sdk/react-native/v10/features/rich-content/voice-recordings-and-audio-playback/)
- [Polls](https://getstream.io/chat/docs/sdk/react-native/v10/features/rich-content/polls/)
- [Link previews](https://getstream.io/chat/docs/sdk/react-native/v10/features/rich-content/link-previews/)
- [Commands and Giphy](https://getstream.io/chat/docs/sdk/react-native/v10/features/rich-content/commands-and-giphy/)
- [Custom composer](https://getstream.io/chat/docs/sdk/react-native/v10/ui-cookbook/composer/custom-composer/), [Custom suggestions](https://getstream.io/chat/docs/sdk/react-native/v10/ui-cookbook/composer/custom-suggestions/) and [Commands UI](https://getstream.io/chat/docs/sdk/react-native/v10/ui-cookbook/composer/commands-ui/)

---

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react-native/v10/ui/components/message-composer-ui/](https://getstream.io/chat/docs/sdk/react-native/v10/ui/components/message-composer-ui/).