# 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 {attachment.name}; } 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 {attachment.name}; } 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)