# Incoming vs Outgoing Message Styles

It's common to want to have different colors for incoming and outgoing messages. The SDK's default theme uses blue for outgoing and grey color for incoming messages (based on `isMyMessage`). The difference reaches much further than the bubble — the surfaces nested inside it follow too, including attachments, link previews, quoted replies, poll bars and the thread connector.

Two mechanisms control how each side looks:

- **Semantic tokens** are the theme's named color roles. Every surface that changes with the side resolves from a **pair** of them — `chat*Outgoing` and `chat*Incoming` — so overriding one token recolors that surface on that side, everywhere it appears. This is the broadest tool, and the one to reach for first.
- **`myMessageTheme`** is a `Channel` prop that layers a theme over own-message rows only. Use it when semantic tokens are not enough or when you want to override global tokens — like `accentPrimary` — on the own-message level only.

This guide walks through restyling the outgoing side with an orange palette, one layer of the theming system at a time — starting with semantic tokens and only reaching for `myMessageTheme` when a surface can't be reached from the layer above.

This is the conversation we are starting from, on the default theme. Note how much of it is blue: the bubbles, the image and file surfaces inside them, the read receipts, the poll progress bars and vote buttons, the thread reply count and the link preview.

| Messages and attachments                                                                                                                                                        | Poll and link preview                                                                                                                                                     |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ![Default theme, messages and attachments](https://getstream.io/docs-assets/images/c0bacb7df08b.png) | ![Default theme, poll and link preview](https://getstream.io/docs-assets/images/1c86d56240bd.png) |

## Best Practices

- Start at the semantic-token layer. One value recolors every component that reads it, so most palette changes need no component overrides at all.
- Declare light and dark values separately — several of these tokens invert between schemes.
- Keep the theme object stable and rebuild it only when the color scheme changes.
- Use `myMessageTheme` when semantic tokens don't provide enough flexibility.

## Step 1: Semantic tokens

Semantic tokens are the middle tier of the theme — named roles like "the outgoing bubble background" that individual components read, rather than raw colors or per-component style objects. Every surface that changes with the side of the conversation resolves from a **pair** of them: `chat*Outgoing` for your own messages, `chat*Incoming` for everybody else's.

Because the components already read these tokens, overriding them is enough to restyle the entire outgoing message UI: bubble, attachments, quoted replies, poll bars and thread connectors all follow.

Build the theme from `useColorScheme` and pass it to `Chat` through the `style` prop.

```tsx
import { useEffect, useState } from "react";
import { ColorSchemeName, useColorScheme } from "react-native";
import { Chat } from "stream-chat-react-native";
import type { DeepPartial, Theme } from "stream-chat-react-native";

const lightSemantics = {
  chatBgOutgoing: "#FFE0B2",
  chatBgAttachmentOutgoing: "#FFB74D",
  chatThreadConnectorOutgoing: "#FFE0B2",
  chatReplyIndicatorOutgoing: "#FF9800",
  chatBorderOnChatOutgoing: "#FF9800",
  chatPollProgressFillOutgoing: "#FF9800",
  chatPollProgressTrackOutgoing: "#FFCC80",
  accentPrimary: "#FF9800",
  textLink: "#FF9800",
};

const darkSemantics = {
  chatBgOutgoing: "#3E2200",
  chatBgAttachmentOutgoing: "#6D3C00",
  chatThreadConnectorOutgoing: "#6D3C00",
  chatReplyIndicatorOutgoing: "#FF9800",
  chatBorderOnChatOutgoing: "#FFA726",
  chatPollProgressFillOutgoing: "#FFA726",
  chatPollProgressTrackOutgoing: "#7A4A00",
  accentPrimary: "#FFA726",
  textLink: "#FF9800",
};

const getTheme = (colorScheme: ColorSchemeName): DeepPartial<Theme> => ({
  semantics: colorScheme === "dark" ? darkSemantics : lightSemantics,
});

export const App = () => {
  const colorScheme = useColorScheme();
  const [theme, setTheme] = useState(getTheme(colorScheme));

  useEffect(() => {
    setTheme(getTheme(colorScheme));
  }, [colorScheme]);

  return (
    <Chat client={client} style={theme}>
      {/* ... */}
    </Chat>
  );
};
```

That is the whole of Step 1. Every own-message surface is now orange, in both schemes, with no component overridden:

| Light                                                                                                                                                                                       | Dark                                                                                                                                                                                      |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ![Custom theme in light mode, messages and attachments](https://getstream.io/docs-assets/images/876b9ccff538.png) | ![Custom theme in dark mode, messages and attachments](https://getstream.io/docs-assets/images/c4089b66435e.png) |
| ![Custom theme in light mode, poll and link preview](https://getstream.io/docs-assets/images/9dc2d020425d.png)       | ![Custom theme in dark mode, poll and link preview](https://getstream.io/docs-assets/images/9e27162c51b2.png)       |

Two notes on the snippet:

- **`accentPrimary` and `textLink` are not side-specific tokens.** Setting them here applies to both sides. If you wish to override them for outgoing messages only, see the next step, where we show how to use `myMessageTheme`.
- **Incoming messages are untouched** — the grey bubble and the received text keep their defaults, because nothing in the snippet names an `*Incoming` token.

## Step 2: A dark outgoing bubble

For more complex customizations, like different outgoing and incoming text colors, semantic tokens are not enough. Let's set semantic tokens for the fields we have, and notice how we can't set `textLink` globally any more, because incoming and outgoing messages need a different color.

### 2.1 Different incoming and outgoing text colors

So far the outgoing text has been the SDK default, which meant it always matched the scheme. Now we set `chatTextOutgoing` ourselves and let the outgoing side run _against_ the scheme: a dark bubble with light text in light mode, and a light bubble with dark text in dark mode. Incoming messages keep their defaults in both.

```tsx
const lightSemantics = {
  chatBgOutgoing: "#C75000",
  chatBgAttachmentOutgoing: "#A34200",
  chatThreadConnectorOutgoing: "#A34200",
  chatReplyIndicatorOutgoing: "#FFB74D",
  chatBorderOnChatOutgoing: "#FFB74D",
  chatPollProgressFillOutgoing: "#FFB74D",
  chatPollProgressTrackOutgoing: "#8A3A00",
  chatTextOutgoing: "#FFFFFF",
  accentPrimary: "#FF9800",
};

const darkSemantics = {
  chatBgOutgoing: "#FFE0B2",
  chatBgAttachmentOutgoing: "#FFCC80",
  chatThreadConnectorOutgoing: "#FFCC80",
  chatReplyIndicatorOutgoing: "#C75000",
  chatBorderOnChatOutgoing: "#C75000",
  chatPollProgressFillOutgoing: "#C75000",
  chatPollProgressTrackOutgoing: "#FFCC80",
  chatTextOutgoing: "#3E2200",
  accentPrimary: "#FFA726",
};

const getTheme = (colorScheme: ColorSchemeName): DeepPartial<Theme> => ({
  semantics: colorScheme === "dark" ? darkSemantics : lightSemantics,
});
```

This is as far as semantic tokens get us:

| Light — dark bubble                                                                                                                                       | Dark — light bubble                                                                                                                                     |
| --------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ![Semantic tokens only, light mode](https://getstream.io/docs-assets/images/336b0f9881ca.png) | ![Semantic tokens only, dark mode](https://getstream.io/docs-assets/images/f7fe67fd900b.png) |

Notice how some text colors weren't updated to a light color. We solve this in the next step.

### 2.2 Separate theme for outgoing messages

`myMessageTheme` is a `Channel` prop whose theme is merged in only for rows where the current user is the author. It is where colors go when they cannot be expressed per-side as a semantic token.

```tsx
import { useMemo } from "react";
import { useColorScheme } from "react-native";
import { Channel, MessageList } from "stream-chat-react-native";
import type { DeepPartial, Theme } from "stream-chat-react-native";

export const MyChannel = ({ channel }) => {
  const colorScheme = useColorScheme();

  const myMessageTheme: DeepPartial<Theme> = useMemo(() => {
    const onDarkBubble = colorScheme !== "dark";

    return {
      semantics: {
        textLink: onDarkBubble ? "#FFFFFF" : "#3E2200",
        buttonSecondaryText: onDarkBubble ? "#FFFFFF" : "#3E2200",
        controlPlaybackToggleText: onDarkBubble ? "#FFFFFF" : "#3E2200",
        textSecondary: onDarkBubble ? "#FFE9D9" : "#7A4A00",
        chatWaveformBar: onDarkBubble
          ? "rgba(255, 255, 255, 0.35)"
          : "rgba(26, 27, 37, 0.25)",
        chatWaveformBarPlaying: onDarkBubble ? "#FFFFFF" : "#C75000",
      },
      messageItemView: {
        file: {
          title: { color: onDarkBubble ? "#FFFFFF" : "#3E2200" },
          fileSize: { color: onDarkBubble ? "#FFFFFF" : "#3E2200" },
        },
        replies: {
          messageRepliesText: { color: onDarkBubble ? "#C75000" : "#FFB74D" },
        },
      },
    };
  }, [colorScheme]);

  return (
    <Channel channel={channel} myMessageTheme={myMessageTheme}>
      <MessageList />
    </Channel>
  );
};
```

Both schemes now read correctly, with the outgoing side running against each one:

| Light — dark bubble                                                                                                                                                                       | Dark — light bubble                                                                                                                                                                     |
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ![Step 2.2 in light mode, poll and link preview](https://getstream.io/docs-assets/images/af764f572f73.png)       | ![Step 2.2 in dark mode, poll and link preview](https://getstream.io/docs-assets/images/0085fd8e874c.png)       |
| ![Step 2.2 in light mode, messages and attachments](https://getstream.io/docs-assets/images/1ddb6165e689.png) | ![Step 2.2 in dark mode, messages and attachments](https://getstream.io/docs-assets/images/effaa3049279.png) |


---

This page was last updated at 2026-08-10T16:00:54.048Z.

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react-native/guides/incoming-vs-outgoing-message-styles/](https://getstream.io/chat/docs/sdk/react-native/guides/incoming-vs-outgoing-message-styles/).