Message

Renders a single chat message — role-aware bubble, markdown, chips, attachments, sources, and actions.

How do I center a div?

Use flexbox on the parent: display: flex, then justify-content: center and align-items: center.

Usage guidelines

  • One turn's content — a role-aware bubble composed from plain message fields (role, text, attachments, sources).
  • Rich textMessage.Markdown for assistant markdown; Message.Text reconstructs inline chips from the wire format.
  • Affordances — copy, regenerate and other actions, source pills, timestamps, and error / stopped / loading markers.
  • Selection → chatMessage.Selection lifts highlighted text back into the composer.
  • Get started — see Installation to add the package and copy the component.

Anatomy

The bare nesting — Message and Message.Content carry the structure:

<Message role={role} isLast={isLast} isError={isError}>
  <Message.Content>
    <Message.Text>{text}</Message.Text>
  </Message.Content>
  <Message.Actions>
    <Message.Copy value={text} />
  </Message.Actions>
</Message>

A realistic assistant turn with markdown, sources, and actions:

<Message.Turn sticky>
  <Message role="assistant" isLast isError={false}>
    <Message.Content>
      <Message.Markdown>{content}</Message.Markdown>
      <Message.Sources>
        {sources.map((s) => (
          <Message.Source key={s.url} url={s.url} domain={s.domain} />
        ))}
      </Message.Sources>
    </Message.Content>
    <Message.Actions>
      <Message.Copy value={content} />
      <Message.Action tooltip="Regenerate" onClick={regenerate}>
        <RefreshIcon />
      </Message.Action>
    </Message.Actions>
  </Message>
</Message.Turn>

Performance

Message is compositional — you pass its parts as children — which means the package cannot memoize rows for you: a parent re-render re-creates the children elements, so a memo inside Message would compare fresh trees and never bail. The memo boundary has to be your row component, the one that receives the message object and derives everything inside:

const ChatMessageItem = memo(({ message, isLast, isStreaming }: ChatMessageItemProps) => {
  const { parts } = message;
  // segmentation, part mapping, actions — all derived in here
  return <Message role={message.role} isLast={isLast}>{/* … */}</Message>;
});

{messages.map((message) => (
  <ChatMessageItem
    key={message.id}
    message={message}
    isLast={message.id === lastMessageId}
    isStreaming={message.id === lastMessageId && isStreaming}
  />
))}

Three rules keep the memo effective while a reply streams:

  • Pass the original message object. Finished messages keep reference identity across stream chunks; spreading ({ parts, ...message }) mints a fresh object every render and silently defeats the memo.
  • Make flags per-message. isStreaming should mean this message is streaming — passing the chat-wide status re-renders every row on each status transition.
  • Take callbacks from stable context inside the row, not as inline props from the map.

Done right, a stream chunk re-renders exactly one row. See Streaming performance for the full render model.

Accessibility

  • Role drives data-role, and error/last state drive data-error/data-last on the root, so styling and assistive context stay in sync.
  • Speaker identity is yours to announce. role is an opaque string the package only surfaces as data-role — alignment and color are invisible to assistive tech. Give each message a visually-hidden {role} said prefix, or an aria-label on the root, so a transcript read top-to-bottom attributes its turns.
  • Error / stopped / loading markers are consumer-owned slots: give an inline failure marker role="alert" so it announces immediately, and quieter states ("stopped", "generating…") role="status".
  • Sources render as <a target="_blank" rel="noopener noreferrer">.
  • Actions are real buttons; Message.Action pairs with a tooltip label.

API reference

Every part accepts className and style and emits a bespoke part attribute unless noted. The headless-backed parts — Message, Message.Turn, Message.Text — also accept render (see PrimitiveProps); the rest are consumer-owned styled parts you edit directly.

Message

The root: role-aware container. Renders data-message.

PropTypeDefault
rolestring
(required)
isLastboolean
false
isErrorboolean
false
AttributeValuesDescription
data-messageThe message root.
data-rolestringThe message role you passed (commonly system / user / assistant).
data-errorPresent when isError is true.
data-lastPresent when isLast is true.

Message.Turn

Groups a role's messages into one visual turn. Renders data-message-turn.

PropTypeDefault
stickyboolean

Message.Text

Plain/segmented text: reconstructs inline chips from the wire format. Renders data-message-text.

PropTypeDefault
childrenstring
(required)
renderText(text, index) => ReactNode
renderChip(chip, index) => ReactNode

Message.Markdown

Renders assistant markdown (via the shared Markdown renderer).

PropTypeDefault
childrenstring
(required)

Message.Copy

Copy-to-clipboard action with a copied state.

PropTypeDefault
valuestring
(required)

Message.Action

An action button with a tooltip (styled IconButton).

PropTypeDefault
tooltipstring

Message.Source

A source pill linking out. Renders data-message-source as <a target="_blank" rel="noopener noreferrer">.

PropTypeDefault
urlstring
(required)
domainstring

Message.Attachment

A read-only attachment chip (image hover-card or file icon).

PropTypeDefault
attachment{ url; mediaType?; filename? }
(required)

Message.Selection

A floating toolbar over a text selection within this message.

PropTypeDefault
onAdd(text: string) => void
(required)

Structural parts

Message.Content, Message.Actions, Message.Attachments, Message.Sources, Message.Error, Message.Stopped, and Message.Loading are consumer-owned layout/marker slots taking only children; they render data-message-content, "message-actions", "message-attachments", "message-sources", "message-error", "message-stopped", and "message-loading" respectively — plain styled elements in your copy of the component, not package primitives.

Hooks

PropTypeDefault
useMessageSelection(scope) => MessageSelection | null
useMessageSelectionScope() => { anchorRef, contentElement }