Chat - Message parts
Render reasoning, sources, files, step markers, and data parts with your own plain React markup.
Each message part type produced by the streaming protocol gets its own rendering branch.
Every assistant message has a parts array where each entry is a typed object.
Core does not render these parts for you—you branch on part.type and render whatever you want.
Key concepts
The part type union
Each part in message.parts has a type discriminant:
message.parts.map((part, index) => {
switch (part.type) {
case 'text':
return <p key={index}>{part.text}</p>;
case 'reasoning':
return (
<details key={index}>
<summary>Thinking</summary>
{part.text}
</details>
);
case 'file':
return (
<a key={index} href={part.url}>
{part.filename ?? 'File'}
</a>
);
case 'source-url':
return (
<a key={index} href={part.url}>
{part.title ?? part.url}
</a>
);
case 'source-document':
return <cite key={index}>{part.title}</cite>;
case 'tool':
return <pre key={index}>{JSON.stringify(part.toolInvocation, null, 2)}</pre>;
case 'step-start':
return <hr key={index} />;
default:
// data-* parts and custom parts
return <pre key={index}>{JSON.stringify(part, null, 2)}</pre>;
}
});
Built-in part types
| Part type | Key fields | Description |
|---|---|---|
text |
text, state |
Plain text content |
reasoning |
text, state |
Chain-of-thought trace |
file |
mediaType, url, filename |
Inline file |
source-url |
sourceId, url, title |
URL citation |
source-document |
sourceId, title, text |
Document citation |
tool |
toolInvocation |
Tool call with state lifecycle |
dynamic-tool |
toolInvocation |
Unregistered tool call |
step-start |
(none) | Step boundary marker |
data-* |
type, data, transient |
Custom data payload |
Streaming state on parts
Text and reasoning parts have an optional state field:
'streaming'— the part is still receiving deltas'done'— the part is complete
Use part.state to show a typing indicator or pulsing cursor while content is arriving.
Key takeaways
- Message parts are typed data—you own the rendering completely.
- Branch on
part.typein a switch statement to render each variant. - Use
part.stateon text and reasoning parts to show streaming indicators - For app-specific part types, register renderers via
partRenderersor useuseChatPartRenderer()
See also
- See Streaming for details on how chunks produce each part type.
- See Type augmentation for details on registering custom part types.
- See Tool approval and renderers for details on custom renderer registration.