# Message
URL: /docs/primitives/message
Build custom message rendering with content parts, attachments, and hover state.
The Message primitive handles individual message rendering: content parts, attachments, quotes, hover state, and error display. It's the building block inside each message bubble, resolving text, images, tool calls, and more through a parts pipeline.
```tsx
import {
MessagePrimitive,
MessagePartPrimitive,
} from "@assistant-ui/react";
function UserMessage() {
return (
{({ part }) => {
if (part.type === "text") return ;
return null;
}}
);
}
function AssistantMessage() {
return (
AI
{({ part }) => {
if (part.type === "text") return ;
return part.toolUI ?? null;
}}
);
}
function UserText() {
return (
);
}
function AssistantText() {
return (
);
}
```
Quick Start \[#quick-start]
A minimal message with parts rendering:
```tsx
import { MessagePrimitive } from "@assistant-ui/react";
```
`Root` renders a `
` that provides message context and tracks hover state. `Parts` iterates over the message's content parts and renders each one. Without custom components, parts render with sensible defaults: `Text` renders a `
` with `white-space: pre-line` and a streaming indicator, `Image` renders via `MessagePartPrimitive.Image`, and tool calls render nothing unless a tool UI is registered globally or inline. Reasoning, source, file, and audio parts render nothing by default.
Runtime setup: primitives require runtime context. Wrap your UI in `AssistantRuntimeProvider` with a runtime (for example `useLocalRuntime(...)`). See [Pick a Runtime](/docs/runtimes/pick-a-runtime).
Core Concepts \[#core-concepts]
Parts Pipeline \[#parts-pipeline]
`MessagePrimitive.Parts` now prefers a children render function. It gives you the current enriched part state directly, so you can branch inline and return exactly the UI you want:
```tsx
{({ part }) => {
if (part.type === "text") return ;
if (part.type === "image") return ;
if (part.type === "tool-call")
return part.toolUI ?? ;
return null;
}}
```
For most new `MessagePrimitive.Parts` code, prefer the `children` render function. Grouped Chain of Thought is the current exception: it plugs into `MessagePrimitive.Parts` via `components.ChainOfThought`.
Tool Resolution \[#tool-resolution]
Tool call parts resolve in this order:
1. **`tools.Override`**: if provided inline through the deprecated `components` prop, handles **all** tool calls
2. **Globally registered tools**: tools registered via `makeAssistantTool` / `useAssistantToolUI`
3. **`tools.by_name[toolName]`**: per-`MessagePrimitive.Parts` inline overrides from the deprecated `components` prop
4. **`tools.Fallback`**: catch-all for unmatched tool calls from the deprecated `components` prop
5. **`part.toolUI`**: the resolved tool UI exposed directly in the children render function
In the children API, tool and data parts expose resolved UI helpers directly:
```tsx
{({ part }) => {
if (part.type === "tool-call")
return part.toolUI ?? ;
if (part.type === "data")
return part.dataRendererUI ?? null;
return null;
}}
```
Returning `null` still allows registered tool UIs and data renderer UIs to render automatically. Return `<>>` if you want to suppress them entirely.
Components Prop (Deprecated) \[#components-prop-deprecated]
`components` is deprecated. This section only documents it so older code is still understandable:
* `ToolGroup` wraps consecutive tool-call parts
* `ReasoningGroup` wraps consecutive reasoning parts
* `components.ChainOfThought` takes over all reasoning and tool-call rendering (mutually exclusive with `ToolGroup`, `ReasoningGroup`, `tools`, and `Reasoning`). Despite the deprecation of `components` in general, this is still the current way to wire grouped Chain of Thought.
* `data.by_name` and `data.Fallback` let you route custom data part types
* `Quote` renders quoted message references from metadata
* `Empty` and `Unstable_Audio` are available for edge and experimental rendering paths
```tsx
(
),
Image: () => ,
File: () => File part
,
tools: {
by_name: {
get_weather: () => Weather tool
,
},
Fallback: ({ toolName }) => Unknown tool: {toolName}
,
},
data: {
by_name: {
"my-event": ({ data }) => {JSON.stringify(data, null, 2)},
},
Fallback: ({ name }) => Unknown data event: {name}
,
},
ToolGroup: ({ children }) => (
{children}
),
ReasoningGroup: ({ children }) => (
Reasoning
{children}
),
Empty: () => ...,
Unstable_Audio: () => null,
}}
/>
```
For new code, use the `children` render function instead.
Hover State \[#hover-state]
`MessagePrimitive.Root` automatically tracks mouse enter/leave events. This hover state is consumed by `ActionBarPrimitive` to implement auto-hide behavior, with no extra wiring needed.
MessagePartPrimitive \[#messagepartprimitive]
Inside your custom part components, use these sub-primitives to access the actual content:
* **`MessagePartPrimitive.Text`**: renders the text content of a text part
* **`MessagePartPrimitive.Image`**: renders the image of an image part
* **`MessagePartPrimitive.InProgress`**: renders only while the part is still streaming
```tsx
function MyText() {
return (
▊
);
}
```
Parts \[#parts]
Root \[#root]
Container for a single message. Renders a `` element unless `asChild` is set.
```tsx
{({ text }) => {text}
}
```
Parts \[#parts-1]
Renders each content part with type-based component resolution.
```tsx
{({ part }) => {
if (part.type === "text") return ;
if (part.type === "image") return ;
if (part.type === "tool-call")
return part.toolUI ?? ;
return null;
}}
```
Content \[#content]
Legacy alias for `Parts`.
```tsx
{({ part }) => {
if (part.type === "text") return ;
return null;
}}
```
PartByIndex \[#partbyindex]
Renders a single part at a specific index.
```tsx
```
Attachments \[#attachments]
Renders all user message attachments.
```tsx
{({ attachment }) => {
if (attachment.type === "image") {
const imageSrc = attachment.content?.find((part) => part.type === "image")?.image;
if (!imageSrc) return null;
return
;
}
if (attachment.type === "document") {
return (
{attachment.name}
);
}
return null;
}}
```
AttachmentByIndex \[#attachmentbyindex]
Renders a single attachment at the specified index within the current message.
```tsx
```
Error \[#error]
Renders children only when the message has an error.
```tsx
```
Quote \[#quote]
Renders quote metadata when the current message includes a quote. Place it above `MessagePrimitive.Parts`.
```tsx
{({ text, messageId }) => (
{text}
)}
```
Unstable\_PartsGrouped \[#unstable\_partsgrouped]
Groups consecutive parts by a custom grouping function \*(unstable)\*.
```tsx
```
Unstable\_PartsGroupedByParentId \[#unstable\_partsgroupedbyparentid]
Groups parts by parent ID \*(unstable, deprecated; use `Unstable_PartsGrouped`)\*.
```tsx
```
If (deprecated) \[#if-deprecated]
Deprecated. Use [`AuiIf`](/docs/api-reference/primitives/assistant-if) instead.
```tsx
// Before (deprecated)
...
...
// After
s.message.role === "user"}>...
s.message.role === "assistant"}>...
```
Patterns \[#patterns]
Custom Text Rendering \[#custom-text-rendering]
```tsx
function MarkdownText() {
return (
);
}
{({ part }) => {
if (part.type === "text") return ;
return null;
}}
```
Tool UI with by\_name \[#tool-ui-with-by\_name]
```tsx
(
Weather
{result?.temperature}°F, {result?.condition}
),
},
Fallback: ({ toolName, status }) => (
{status.type === "running" ? `Running ${toolName}...` : `${toolName} completed`}
),
},
}}
/>
```
Error Display \[#error-display]
```tsx
Something went wrong. Please try again.
```
Error Display with ErrorPrimitive \[#error-display-with-errorprimitive]
For more control over error rendering, `ErrorPrimitive` provides a dedicated component that auto-reads the error string from the message status:
```tsx
import { ErrorPrimitive, MessagePrimitive } from "@assistant-ui/react";
```
`ErrorPrimitive.Root` renders a `` container with `role="alert"` and `ErrorPrimitive.Message` renders a `
` that displays the error text. `Root` always renders. Only `Message` conditionally returns `null` when there is no error. Wrap in `` if you want the entire block to be conditional. See the [ErrorPrimitive API Reference](/docs/api-reference/primitives/error) for full details.
Legacy and Unstable APIs \[#legacy-and-unstable-apis]
* `MessagePrimitive.Unstable_PartsGrouped` and `MessagePrimitive.Unstable_PartsGroupedByParentId` are unstable APIs for custom grouping.
* `Unstable_PartsGroupedByParentId` is deprecated in favor of `Unstable_PartsGrouped`.
Role-Based Styling \[#role-based-styling]
`MessagePrimitive.Root` sets `data-message-id` automatically but does not set a `data-role` attribute. Style by role in your message components:
```tsx
// In your ThreadPrimitive.Messages children render function:
function UserMessage() {
return (
);
}
function AssistantMessage() {
return (
);
}
```
Attachments \[#attachments-1]
```tsx
{({ attachment }) => {
if (attachment.type === "image") {
const imageSrc = attachment.content?.find((part) => part.type === "image")?.image;
if (!imageSrc) return null;
return
;
}
if (attachment.type === "document") {
return (
📄 {attachment.name}
);
}
return null;
}}
```
Relationship to Components \[#relationship-to-components]
The shadcn [Thread](/docs/ui/thread) component renders user and assistant messages built from these primitives. The pre-built `AssistantMessage` and `UserMessage` components handle text rendering, tool UIs, error display, and action bars, all using `MessagePrimitive` under the hood.
Messages are commonly paired with [ActionBar](/docs/primitives/action-bar) for copy/reload/edit actions and [BranchPicker](/docs/primitives/branch-picker) for navigating between alternative responses.
API Reference \[#api-reference]
For full prop details on every part, see the [MessagePrimitive API Reference](/docs/api-reference/primitives/message).
Related:
* [MessagePartPrimitive API Reference](/docs/api-reference/primitives/message-part)
* [ActionBarPrimitive API Reference](/docs/api-reference/primitives/action-bar)
* [BranchPickerPrimitive API Reference](/docs/api-reference/primitives/branch-picker)