# Message UI

Each message in a message list is rendered in two steps: `Message` prepares the message and what can be done with it, and a message view, `MessageItemView` by default, shows it. Use this page to see how a message is put together, to change what its bubble holds and how its text renders, and to replace parts of a message or the whole message view. The props are in the [Message](https://getstream.io/chat/docs/sdk/react-native/component-reference/message/message/) and [Message UI](https://getstream.io/chat/docs/sdk/react-native/component-reference/message/message-ui/) references, and the parts and their props are on [Message UI components](https://getstream.io/chat/docs/sdk/react-native/component-reference/message/message-ui-components/).

## How a message is rendered

For each message, the list renders a row: the date separator when a day starts, the message, and the unread separator after the last read message. A system message renders `MessageSystem` instead of a message. For every other message the list renders `Message`, which:

1. creates the message's handlers: react, reply, edit, delete, flag, mute, pin, open the thread, and retry a failed send;
2. puts them in `MessageContext`, with the message and `onLongPress`, which opens the message menu;
3. renders the `MessageItemView` key, by default the SDK's `MessageItemView`.

The message view gets **no props**. It reads the message and its handlers with `useMessageContext()`, and so does any component it renders. The context also holds `messageActionHandlers`, the handlers behind the message's actions, which the message menu reads; see [Message actions in your own components](https://getstream.io/chat/docs/sdk/react-native/ui/components/actions/#message-actions-in-your-own-components).

A message that moderation blocked skips the message view: `Message` renders `MessageBlocked` in its place, with the message as a prop.

## Parts of the default message view

The default `MessageItemView` is made of parts, each its own override key:

| Part                                                                                   | Override key                                                                                                              |
| -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| The sender's avatar, on other users' messages, once for each group of messages         | `MessageAuthor`                                                                                                           |
| The pinned, reminder, saved-for-later and also-sent-in-channel marks above the message | `MessageHeader`, with `MessagePinnedHeader`, `MessageReminderHeader`, `MessageSavedForLaterHeader`, `SentToChannelHeader` |
| The bubble with its content                                                            | `MessageContent`, and the [content items](#bubble-content)                                                                |
| A quoted message                                                                       | `Reply`                                                                                                                   |
| Images and videos, files, other attachments                                            | `Gallery`, `FileAttachmentGroup`, `Attachment`                                                                            |
| The text of an AI reply as it streams in                                               | `StreamingMessageView`                                                                                                    |
| Reactions, above or below the bubble                                                   | `ReactionListTop`, `ReactionListBottom`                                                                                   |
| The replies button, in a channel                                                       | `MessageReplies`, with `MessageRepliesAvatars`                                                                            |
| The sender's name, the status, the timestamp and the "Edited" mark                     | `MessageFooter`, with `MessageStatus` and `MessageTimestamp`                                                              |
| The error badge on a message that failed                                               | `MessageError`                                                                                                            |
| A deleted message                                                                      | `MessageDeleted`                                                                                                          |
| A blocked message                                                                      | `MessageBlocked`                                                                                                          |
| A message bounced by moderation                                                        | `MessageBounce`                                                                                                           |

`MessageSpacer` has no default. When you set it, it renders last in each message row, outside the bubble. `MessageContentTopView`, `MessageContentBottomView`, `MessageContentLeadingView` and `MessageContentTrailingView` have no default either: they render around the content, inside the bubble.

### The footer and the status

`MessageFooter` shows, from start to end, the sender's name on other users' messages in a group channel, the status mark on the user's own messages, the time, and "Edited" on an edited message. A message that is not the last of its group has no footer, unless it is still sending or failed. The status mark is one of:

- a clock, while the message is sending;
- a check, once the server has it;
- a double check, once another member received it;
- a double check in a color of its own, once another member read it.

`MessageStatus` shows only the sending mark while the channel's read events are turned off, because the other marks come from other members' read and delivery events. It shows nothing on a message that failed, on an error message, or on a deleted one. The status shows on the user's own messages, including the parent message at the top of a thread when the user sent it. `showMessageStatus` on `Message` turns it on or off for a message.

### Deleted, blocked and failed messages

- **A deleted message** shows "Message deleted" in place of its bubble: `MessageDeleted`, without actions. It keeps the footer, with the time, and no status mark. A message counts as deleted when its `type` is `'deleted'`.
- **A blocked message** shows its text in a pill, with no avatar, time or status: `MessageBlocked`. A message is blocked when its `type` is `'error'` and moderation removed it (`moderation.action` is `'remove'`).
- **A message that failed to send** shows an error badge at the edge of the bubble, `MessageError`. Pressing the message opens the message menu, so the user can retry or delete it. A message the server bounced opens `MessageBounce` on a press or a long press, which asks the user to send it anyway, edit it or delete it.

Some messages render differently:

- **A poll** renders inside the message's bubble, with the message's text and attachments, as in [Bubble content](#bubble-content).
- **A system message**, such as "Cleo joined the channel", renders the `MessageSystem` component instead of a message.
- **An AI-generated message**, as the `isMessageAIGenerated` prop of `Chat` decides, renders its text with `StreamingMessageView`, which shows the text as it streams in.

## Bubble content

The bubble holds the message's content, in the order `messageContentOrder` on `Channel` and `Thread` gives. Each item renders only when the message has the corresponding property. The default order is:

| Item (`MessageContentType`) | Shows when                                                              | Renders                                                              |
| --------------------------- | ----------------------------------------------------------------------- | -------------------------------------------------------------------- |
| `quoted_reply`              | The message quotes another one, and none of its attachments has actions | The `Reply` key. Pressing it goes to the original message.           |
| `gallery`                   | The message has images or videos                                        | The `Gallery` key                                                    |
| `files`                     | The message has files                                                   | The `FileAttachmentGroup` key                                        |
| `poll`                      | The message has a poll: `message.poll_id`                               | The poll                                                             |
| `ai_text`                   | `isMessageAIGenerated` marks the message as AI generated                | The `StreamingMessageView` key                                       |
| `attachments`               | The message has attachments other than images, videos and files         | The `Attachment` key, once for each attachment                       |
| `location`                  | The message has a shared location: `message.shared_location`            | The `MessageLocation` key. It has no default, so nothing without it. |
| `text`                      | The message has text, and isn't AI generated                            | The text of the message                                              |

An item left out of `messageContentOrder` doesn't render. A message with none of its items, and that isn't deleted, renders nothing.

Here is a `TextFirstMessages` component whose messages show their text above the attachments, and no location:

```tsx title="src/TextFirstMessages.tsx"
import type { Channel as StreamChannel } from "stream-chat";
import {
  Channel,
  MessageComposer,
  MessageList,
} from "stream-chat-react-native";
import type { MessageContentType } from "stream-chat-react-native";

// Declared once, so every render passes the same array.
const messageContentOrder: MessageContentType[] = [
  "quoted_reply",
  "text",
  "poll",
  "gallery",
  "files",
  "attachments",
  "ai_text",
];

export const TextFirstMessages = ({ channel }: { channel: StreamChannel }) => (
  <Channel channel={channel} messageContentOrder={messageContentOrder}>
    <MessageList />
    <MessageComposer />
  </Channel>
);
```

`Thread` takes the same prop, so pass it to both.

## The text

The text of a message renders as Markdown, translated into the user's language when the message has a translation for it. Three things change it:

- `markdownRules` on `Channel` and `Thread` replaces the rules that turn Markdown into components.
- `messageTextNumberOfLines` limits how many lines of text a message shows.
- The `MessageText` key replaces the rendering of the text. It has no default. It gets the props of the text container, and `renderText`, the function the default uses, so it can render the default with changes.

The theme's `messageItemView.content.markdown` key styles the Markdown. See [Theming](https://getstream.io/chat/docs/sdk/react-native/features/app-experience/theming/).

## Pressing a message

Pressing and long pressing a message go through two handlers of `Message`. `onLongPress` opens the message menu, except for a blocked message, a message with attachment actions, or when `enableLongPress` is `false`. `onPress` dismisses the keyboard when `dismissKeyboardOnMessageTouch` is on, and opens the menu for a message that failed. `onPressMessage` and `onLongPressMessage` on `Channel` and `Thread` receive each press with a `defaultHandler`, so they add to the default or replace it. A press on a message that failed doesn't reach `onPressMessage`. The message menu is on [Overlays and bottom sheets](https://getstream.io/chat/docs/sdk/react-native/ui/components/overlays-and-bottom-sheets/#the-message-menu).

Swiping a message toward the start quotes it: `enableSwipeToReply`, on by default, turns this on and off, and `customMessageSwipeAction` replaces what the swipe does. `MessageSwipeContent` is the icon that shows while swiping.

## Replacing parts

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

| Override key                                                                        | Replaces                                                                                     |
| ----------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
| `MessageItemView`                                                                   | A message's look. Gets no props; reads `useMessageContext()`.                                |
| `Message`                                                                           | A whole message, its handlers included. Gets the message and its place in the list as props. |
| The keys in [Parts of the default message view](#parts-of-the-default-message-view) | One part of a message                                                                        |
| `MessageSystem`                                                                     | A system message                                                                             |

Here is a `CompactMessages` list that shows each message as one compact line:

```tsx title="src/CompactMessages.tsx"
import { Pressable, StyleSheet, Text, View } from "react-native";
import {
  MessageList,
  MessageTextContainer,
  useMessageContext,
  WithComponents,
} from "stream-chat-react-native";

const CompactMessage = () => {
  const { contextMenuAnchorRef, message, onLongPress } = useMessageContext();

  return (
    // The default view opens the message menu on a long press; a view of your own does it too.
    <Pressable
      onLongPress={(event) => onLongPress({ emitter: "messageContent", event })}
    >
      <View ref={contextMenuAnchorRef} style={styles.row}>
        <Text style={styles.name}>
          {message.user?.name ?? message.user?.id}
        </Text>
        <MessageTextContainer />
      </View>
    </Pressable>
  );
};

const overrides = { MessageItemView: CompactMessage };

export const CompactMessages = () => (
  <WithComponents overrides={overrides}>
    <MessageList />
  </WithComponents>
);

const styles = StyleSheet.create({
  name: { fontWeight: "600" },
  row: {
    flexDirection: "row",
    gap: 8,
    paddingHorizontal: 16,
    paddingVertical: 4,
  },
});
```

`MessageTextContainer` reads the message from `MessageContext` too, so it needs no props. The message menu opens from the `onLongPress` in the context, which `Message` created. It positions itself on the view in `contextMenuAnchorRef`. The [Custom message UI cookbook](https://getstream.io/chat/docs/sdk/react-native/ui-cookbooks/message-list/custom-message/) builds a complete message view.

## Used in

- [Sending, editing and deleting](https://getstream.io/chat/docs/sdk/react-native/features/messaging/sending-editing-and-deleting/)
- [Reactions](https://getstream.io/chat/docs/sdk/react-native/features/messaging/reactions/)
- [Quoted replies](https://getstream.io/chat/docs/sdk/react-native/features/messaging/quoted-replies/)
- [Mentions](https://getstream.io/chat/docs/sdk/react-native/features/messaging/mentions/)
- [Custom message](https://getstream.io/chat/docs/sdk/react-native/ui-cookbooks/message-list/custom-message/)

---

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