Skip to content
+

Chat - Text and markdown

Render plain text and markdown content in chat messages with the built-in renderer.

Text parts are the most common message part. Every message with written content—whether from a human user or an AI assistant—uses one or more text parts to carry that content.

Text part structure

A text part is represented by the ChatTextMessagePart interface:

interface ChatTextMessagePart {
  type: 'text';
  text: string;
  state?: 'streaming' | 'done';
}

A message can contain multiple text parts alongside other part types (files, sources, tool calls). The parts array on ChatMessage holds them all:

const message: ChatMessage = {
  id: 'msg-1',
  role: 'assistant',
  parts: [
    { type: 'text', text: 'Here is the analysis you requested:' },
    {
      type: 'file',
      mediaType: 'image/png',
      url: '/chart.png',
      filename: 'chart.png',
    },
    { type: 'text', text: 'The chart shows a 15% increase in Q4.' },
  ],
};

The reasoning part shares the same structure (text plus optional state) and is rendered with the same markdown renderer — see Reasoning.

Markdown rendering

When using ChatBox with the Material UI layer (@mui/x-chat), text parts are rendered through a built-in markdown parser that converts common markdown syntax into React elements. This happens automatically, with no configuration needed.

The default renderer is streaming-aware: it repairs half-streamed markdown as tokens arrive, so partial syntax renders cleanly instead of leaking raw ** or unclosed code-fence markers mid-stream.

The built-in parser supports:

  • Bold (**text** or __text__)
  • Italic (*text* or _text_)
  • Inline code (`code`)
  • Links ([label](url))
  • Images (![alt](url))
  • Headings (# H1 through ###### H6)
  • Ordered and unordered lists
  • Code fences (rendered as ChatCodeBlock)
  • Footnote citations ([^1])
Markdown source
Rendered message
Assistant

Markdown rendering

The built-in renderer turns model output into bold, italic, and inline code.

  • First item
  • Second item with a link
  • Third item
ts
const greeting: string = 'Hello, markdown!';
console.log(greeting);

Footnotes are supported too.1

<b>Raw HTML stays escaped text.</b>

1: This is the footnote definition.

Customizing text rendering

Override the markdown renderer through partProps.text.renderText on ChatMessageContent:

<ChatBox
  adapter={adapter}
  slotProps={{
    content: {
      partProps: {
        text: {
          renderText: (text) => <MyCustomMarkdownRenderer content={text} />,
        },
      },
    },
  }}
/>

renderText receives the part's raw text string and returns any React node — the example below in Streaming text shows what you give up when replacing the built-in renderer.

This lets you plug in any markdown library (react-markdown, remark, MDX) while keeping the rest of the chat UI intact. Replacing renderText replaces the entire pipeline for that part — including streaming repair and code-fence routing. Code fences are rendered by the same built-in renderer, so a custom renderText also takes over code-block rendering — see Code blocks for rendering fences with your own component.

Streaming text

Text parts support incremental delivery through the streaming protocol. The stream uses three chunk types to build up a text part:

Chunk type Purpose
text-start Opens a new text part
text-delta Appends a string fragment
text-end Marks the text part as complete

While tokens are arriving, the part's state field is 'streaming'. Once the text-end chunk arrives, state transitions to 'done', as shown below:

// During streaming
{ type: 'text', text: 'The answer is', state: 'streaming' }

// After completion
{ type: 'text', text: 'The answer is 42.', state: 'done' }
Streaming markdown

Assistant

Send anything — I reply with streaming markdown.

Press Enter to start editing

When composing a fully custom message renderer (for example with the headless package), use the part's state field to show a typing indicator or pulsing cursor while content is arriving:

function TextPartDisplay({ part }: { part: ChatTextMessagePart }) {
  return (
    <span>
      {part.text}
      {part.state === 'streaming' && <span className="cursor" />}
    </span>
  );
}

The built-in Material UI renderer already handles this: it stays streaming-aware and repairs partial markdown automatically (see Markdown rendering). Note that partProps.text.renderText receives only the raw text string — if you need the part's state, you must take over part rendering entirely.

See also

  • See Scrolling for how the message list follows streaming content.
  • See Reasoning for the reasoning part, which shares the text part's structure and renderer.
  • See Code blocks for details on syntax-highlighted code fence rendering.
  • See Message appearance for details on the visual presentation of the message list.

API

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