React UI Base
Loading...

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

What's the weather like in San Francisco?
Image 1
It's currently 72°F and partly cloudy in San Francisco.
72°F
San Francisco
Partly cloudy
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

PropTypeDefaultDescription
messageReactTamboThreadMessageMessage data for content blocks and metadata. Provide message or id.
idstringMessage ID to look up from the current thread. Provide message or id.
isLoadingboolean!isIdleOverride loading state. Defaults to the thread idle state from useTambo.

Render state:

FieldTypeDescription
idstringThe message ID.
role"user" | "assistant"Role derived from the message.
isLoadingbooleanWhether the message is in a loading state.
messageReactTamboThreadMessageThe full message object.
reasoningbooleanWhether the message is in a reasoning state.
reasoningMsnumber | nullTime in milliseconds spent reasoning, or null if not applicable.
hasTextbooleanWhether the message has text/markdown content.
hasToolUsebooleanWhether the message has a tool call.
hasToolResultbooleanWhether the message has a tool result.
hasComponentbooleanWhether the message has a rendered component.
hasResourcebooleanWhether the message has a resource.

Content

PropTypeDefaultDescription
messageContentstring | TamboThreadMessage["content"]Override the content to render instead of the message content.
renderAsMarkdownbooleantrueWhether to convert content to a Markdown string.
keepMountedbooleanfalseKeep mounted when there is no content and hide via data-hidden.

Render state:

FieldTypeDescription
loadingbooleanWhether loading with no content and not reasoning.
reasoningbooleanWhether the message is in reasoning state.
hasContentbooleanWhether the message has any displayable content.
markdownbooleanWhether content is rendered as Markdown.
contentstring | TamboThreadMessage["content"]The resolved content to render.
contentAsMarkdownStringstring | undefinedContent as a single Markdown string. Undefined if renderAsMarkdown is false.

Images

PropTypeDefaultDescription
renderImage(props: MessageImageRenderFnProps) => React.ReactNodeCustom renderer for each image. Receives { url, index, alt }.
keepMountedbooleanfalseKeep mounted when there are no images and hide via data-hidden.

Render state:

FieldTypeDescription
imagesstring[]Array of image URLs to display.

LoadingIndicator

PropTypeDefaultDescription
keepMountedbooleanfalseKeep 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:

FieldTypeDescription
hasRenderedComponentbooleanWhether 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:

FieldTypeDescription
renderedComponentsReact.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:

FieldTypeDescription
canvasExistsbooleanWhether a canvas element exists in the DOM.
hasRenderedComponentbooleanWhether a rendered component exists.

Accessibility

  • Keep image alt text meaningful when providing custom image renderers.
  • Ensure rendered-component controls stay keyboard reachable.
  • LoadingIndicator renders as a <div> by default. Override with the render prop 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-hidden on Content, Images, LoadingIndicator when keepMounted is used