Message
Compose message shell rendering for text, images, loading state, and rendered component content.
Message
Message in @tambo-ai/react-ui-base handles message-level behavior and content selection while leaving styling to your app or @tambo-ai/ui-registry.
Demo
import { Message } from "@tambo-ai/react-ui-base/message";import type { TamboThreadMessage } from "@tambo-ai/react";import { Bot, User } from "lucide-react";export function MessageBubble({ message, role, isLoading,}: { message: TamboThreadMessage; role: "user" | "assistant"; isLoading?: boolean;}) { return ( <Message.Root message={message} role={role} isLoading={isLoading} className="flex gap-2.5 [[data-role=user]&]:flex-row-reverse" > <div className="flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-neutral-200 text-neutral-600 [[data-role=user]_&]:bg-neutral-900 [[data-role=user]_&]:text-white dark:bg-neutral-700 dark:text-neutral-300"> <User className="hidden h-3.5 w-3.5 [[data-role=user]_&]:block" /> <Bot className="hidden h-3.5 w-3.5 [[data-role=assistant]_&]:block" /> </div> <div className="flex max-w-[80%] flex-col gap-2 rounded-2xl rounded-tl-md bg-neutral-200/70 px-4 py-3 text-neutral-900 [[data-role=user]_&]:rounded-2xl [[data-role=user]_&]:rounded-tr-md [[data-role=user]_&]:bg-neutral-900 [[data-role=user]_&]:text-white dark:bg-neutral-700 dark:text-neutral-100"> <Message.Content className="text-sm" /> <Message.Images className="flex gap-2 [&_img]:max-h-40 [&_img]:rounded-lg [&_img]:object-cover" /> <Message.RenderedComponent> <Message.RenderedComponentContent /> </Message.RenderedComponent> <Message.LoadingIndicator className="flex items-center gap-1" /> </div> </Message.Root> );}Anatomy
<Message.Root message={message}>
<Message.Content />
<Message.Images />
<Message.LoadingIndicator />
<Message.RenderedComponent>
<Message.RenderedComponentCanvasButton />
<Message.RenderedComponentContent />
</Message.RenderedComponent>
</Message.Root>Examples
Identifying a Message by ID
Instead of passing the full message object, you can pass id and the component will look it up from the thread:
<Message.Root id={messageId}>
<Message.Content />
</Message.Root>Custom Image Rendering
<Message.Images
renderImage={({ url, index }) => <img key={index} src={url} alt="" />}
/>Root Render State
Use the render prop on Root to access content flags:
<Message.Root
message={message}
render={(props, state) => (
<div {...props}>
{state.reasoning && <p>Thinking...</p>}
{state.hasComponent && <p>Component generated</p>}
</div>
)}
/>API reference
Root
| Prop | Type | Default | Description |
|---|---|---|---|
message | ReactTamboThreadMessage | — | Message data for content blocks and metadata. Provide message or id. |
id | string | — | Message ID to look up from the current thread. Provide message or id. |
isLoading | boolean | !isIdle | Override loading state. Defaults to the thread idle state from useTambo. |
Render state:
| Field | Type | Description |
|---|---|---|
id | string | The message ID. |
role | "user" | "assistant" | Role derived from the message. |
isLoading | boolean | Whether the message is in a loading state. |
message | ReactTamboThreadMessage | The full message object. |
reasoning | boolean | Whether the message is in a reasoning state. |
reasoningMs | number | null | Time in milliseconds spent reasoning, or null if not applicable. |
hasText | boolean | Whether the message has text/markdown content. |
hasToolUse | boolean | Whether the message has a tool call. |
hasToolResult | boolean | Whether the message has a tool result. |
hasComponent | boolean | Whether the message has a rendered component. |
hasResource | boolean | Whether the message has a resource. |
Content
| Prop | Type | Default | Description |
|---|---|---|---|
messageContent | string | TamboThreadMessage["content"] | — | Override the content to render instead of the message content. |
renderAsMarkdown | boolean | true | Whether to convert content to a Markdown string. |
keepMounted | boolean | false | Keep mounted when there is no content and hide via data-hidden. |
Render state:
| Field | Type | Description |
|---|---|---|
loading | boolean | Whether loading with no content and not reasoning. |
reasoning | boolean | Whether the message is in reasoning state. |
hasContent | boolean | Whether the message has any displayable content. |
markdown | boolean | Whether content is rendered as Markdown. |
content | string | TamboThreadMessage["content"] | The resolved content to render. |
contentAsMarkdownString | string | undefined | Content as a single Markdown string. Undefined if renderAsMarkdown is false. |
Images
| Prop | Type | Default | Description |
|---|---|---|---|
renderImage | (props: MessageImageRenderFnProps) => React.ReactNode | — | Custom renderer for each image. Receives { url, index, alt }. |
keepMounted | boolean | false | Keep mounted when there are no images and hide via data-hidden. |
Render state:
| Field | Type | Description |
|---|---|---|
images | string[] | Array of image URLs to display. |
LoadingIndicator
| Prop | Type | Default | Description |
|---|---|---|---|
keepMounted | boolean | false | Keep mounted when not loading and hide via data-hidden. |
Renders three <span> elements with data-dot="1", data-dot="2", data-dot="3" for styling animated dots. Only visible when the parent Message.Root is loading with no content and not reasoning.
RenderedComponent
No custom props. Renders only for assistant messages that have component content blocks.
Render state:
| Field | Type | Description |
|---|---|---|
hasRenderedComponent | boolean | Whether a rendered component exists. |
role | "user" | "assistant" | The message role. |
RenderedComponentContent
No custom props. Renders the actual React nodes from component content blocks.
Render state:
| Field | Type | Description |
|---|---|---|
renderedComponents | React.ReactNode[] | Array of rendered component React nodes. |
RenderedComponentCanvasButton
No custom props. Renders a <button> that dispatches a tambo:showComponent custom event to display the component in a canvas area. Only enabled when a canvas element ([data-canvas-space="true"]) exists and the message has a rendered component.
Render state:
| Field | Type | Description |
|---|---|---|
canvasExists | boolean | Whether a canvas element exists in the DOM. |
hasRenderedComponent | boolean | Whether a rendered component exists. |
Accessibility
- Keep image
alttext meaningful when providing custom image renderers. - Ensure rendered-component controls stay keyboard reachable.
LoadingIndicatorrenders as a<div>by default. Override with therenderprop for semantic markup.
Styling Hooks
data-slot="message-root"data-slot="message-content"data-slot="message-images"data-slot="loading-indicator"data-slot="message-rendered-component-area"data-slot="message-rendered-component-content"data-slot="rendered-component-canvas-button"data-hiddenonContent,Images,LoadingIndicatorwhenkeepMountedis used