# renderText function

`renderText` controls how message text is formatted. By default it parses Markdown and returns a `ReactElement` using `ReactMarkdown` from [react-markdown](https://github.com/remarkjs/react-markdown), with the `remark` parser and `remark`/`rehype` plugins.

The default `remark` plugins used by SDK are:

1. [`remark-gfm`](https://github.com/remarkjs/remark-gfm) - a third party plugin to add GitHub-like markdown support

The default `rehype` plugins (both specific to this SDK) are:

1. plugin to render user mentions
2. plugin to render emojis

## Best Practices

- Keep Markdown enabled unless your product requires strict plain text.
- Sanitize or restrict allowed tags when rendering untrusted content.
- Add custom plugins sparingly to avoid performance regressions.
- Keep mention and emoji rendering consistent across message views.
- Test custom renderers with long messages and mixed formatting.

## Overriding Defaults

### Custom `renderText` Function

If you don’t want Markdown support, pass a custom `renderText` function that returns a React node. You can provide it to [`MessageList`](/chat/docs/sdk/react/components/core-components/message_list/) or [`MessageSimple`](/chat/docs/sdk/react/components/message-components/message_ui/) via the `renderText` prop.

For example, here’s a minimal version that wraps text in a `<span>`:

```tsx
import { MessageList } from "stream-chat-react";

const customRenderText = (text) => {
  return <span>{text}</span>;
};

export const WrappedMessageList = () => (
  <MessageList renderText={customRenderText} />
);
```

Here’s an example with [`VirtualizedMessageList`](/chat/docs/sdk/react/components/core-components/virtualized_list/), which doesn’t accept `renderText` directly:

```tsx
import { VirtualizedMessageList, MessageSimple } from "stream-chat-react";

const customRenderText = (text) => {
  return <span>{text}</span>;
};

const CustomMessage = (props) => (
  <MessageSimple {...props} renderText={customRenderText} />
);

export const WrappedVirtualizedMessageList = () => (
  <VirtualizedMessageList Message={CustomMessage} />
);
```

### Custom Element Rendering

If you like the default output but want to customize how specific [ReactMarkdown components](https://github.com/remarkjs/react-markdown#appendix-b-components) render (for example, `**strong**`), pass options as the third argument to `renderText`:

<admonition type="note">

`mention` and `emoji` are special component types generated by the SDK’s custom rehype plugins.

</admonition>

```tsx
import { renderText } from "stream-chat-react";

const CustomStrongComponent = ({ children }) => (
  <b className="custom-strong-class-name">{children}</b>
);

const CustomMentionComponent = ({ children, node: { mentionedUser } }) => (
  <a data-user-id={mentionedUser.id} href={`/user-profile/${mentionedUser.id}`}>
    {children}
  </a>
);

export const WrappedMessageList = () => (
  <MessageList
    renderText={(text, mentionedUsers) =>
      renderText(text, mentionedUsers, {
        customMarkDownRenderers: {
          strong: CustomStrongComponent,
          mention: CustomMentionComponent,
        },
      })
    }
  />
);
```

### Custom Remark and Rehype Plugins

To extend the plugin list, provide custom remark/rehype arrays via `getRemarkPlugins` and `getRehypePlugins`. These receive the default arrays, and you return the final list. Pass them as the third `renderText` parameter.

<admonition type="note">

To understand remark/rehype plugins, start with [`react-remark`](https://github.com/remarkjs/react-remark), which is used under the hood.

</admonition>

```tsx
import { renderText, RenderTextPluginConfigurator } from "stream-chat-react";
import { customRehypePlugin } from "./rehypePlugins";
import { customRemarkPlugin } from "./remarkPlugins";

const getRehypePlugins: RenderTextPluginConfigurator = (plugins) => {
  return [customRehypePlugin, ...plugins];
};
const getRemarkPlugins: RenderTextPluginConfigurator = (plugins) => {
  return [customRemarkPlugin, ...plugins];
};

const customRenderText = (text, mentionedUsers) =>
  renderText(text, mentionedUsers, {
    getRehypePlugins,
    getRemarkPlugins,
  });

const WrappedMessageList = () => <MessageList renderText={customRenderText} />;
```

You can also define a custom set of allowed tag names. For tree transforms, use libraries like [`unist-builder`](https://github.com/syntax-tree/unist-builder) plus [`unist-util-visit`](https://github.com/syntax-tree/unist-util-visit-parents) or [`hast-util-find-and-replace`](https://github.com/syntax-tree/hast-util-find-and-replace):

```tsx
import { findAndReplace } from "hast-util-find-and-replace";
import { u } from "unist-builder";
import {
  defaultAllowedTagNames,
  renderText,
  RenderTextPluginConfigurator,
} from "stream-chat-react";

// wraps every letter b in <xxx></xxx> tags
const customTagName = "xxx";
const replace = (match) =>
  u("element", { tagName: customTagName }, [u("text", match)]);
const customRehypePlugin = () => (tree) => findAndReplace(tree, /b/, replace);

const getRehypePlugins: RenderTextPluginConfigurator = (plugins) => {
  return [customRehypePlugin, ...plugins];
};

const customRenderText = (text, mentionedUsers) =>
  renderText(text, mentionedUsers, {
    allowedTagNames: [...defaultAllowedTagNames, customTagName],
    getRehypePlugins,
  });

const WrappedMessageList = () => <MessageList renderText={customRenderText} />;
```

### Existing Remark and Rehype Plugins

The SDK already exports the following plugins:

**Remark plugins**

- `htmlToTextPlugin`: Treats HTML nodes found in the Markdown as plain text (no HTML is parsed or executed). Useful for preventing inline HTML from rendering.
- `imageToLink`: Converts Markdown images (`![alt](url)`) into links (`<a href="url">label</a>`). The link text defaults to the image URL, but can be configured to use the image `alt` or `title`.
- `keepLineBreaksPlugin`: Preserves multiple blank source lines by inserting `<br>` elements between block nodes so visual spacing matches the original input.
- `plusPlusToEmphasis`: Adds support for “inserted” text using `++text++`, which renders as `<ins>text</ins>`. Skips code, math, and links.
- `remarkIgnoreMarkdown`: Disables Markdown parsing entirely and renders the original input as plain text inside a single paragraph.

**Rehype plugins**

- `mentionsMarkdownPlugin(mentionedUsers)`: Detects `@username`/`@id` mentions (based on the provided `mentionedUsers`) and wraps them in a custom `<mention>` element with a `mentionedUser` payload. Handles e‑mail-like usernames that GFM splits into `mailto:` links.
- `emojiMarkdownPlugin`: Wraps native Unicode emoji in a custom `<emoji>` element, enabling consistent styling/behavior for emoji in rendered output.

<admonition type="note">

`mentionsMarkdownPlugin` is a factory function that needs to receive an array of `UserResponse` objects (`mentionedUsers`) when it is pushed to the rehype plugins array returned by `getRehypePlugins`.

</admonition>

To select which plugins to apply:

```tsx
import {
  keepLineBreaksPlugin,
  MessageList,
  renderText,
  useChannelStateContext,
} from "stream-chat-react";

const customRenderText = (text?: string, mentionedUsers?: UserResponse[]) =>
  renderText(text, mentionedUsers, {
    getRemarkPlugins: () => [keepLineBreaksPlugin],
    // disabled all the SDK's rehype plugins
    getRehypePlugins: () => [],
  });

const WrappedMessageList = () => <MessageList renderText={customRenderText} />;
```


---

This page was last updated at 2026-03-04T14:23:22.199Z.

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