Message
A typical agent conversation
Message is the layout of one turn. It owns the author, avatar, timestamp,
metadata and alignment; MessageContent is the column between the avatar and
the row's edge; a Bubble is always a child and is never built here. The data
slots the row carried before — reasoning, tool calls, citations, streaming —
stay as props on MessageContent and paint outside the bubble surface.
Installation
nub add @gpuixui/componentsUsage
import {
Bubble,
BubbleContent,
Message,
MessageActions,
MessageAvatar,
MessageContent,
MessageFooter,
MessageHeader,
} from "@gpuixui/components";
<Message variant="assistant" timestamp="09:42">
<MessageAvatar />
<MessageContent
reasoning={message.reasoning}
toolCalls={message.tools}
sources={message.sources}
streaming={message.streaming}
>
<MessageHeader />
<Bubble>
<BubbleContent>{message.body}</BubbleContent>
</Bubble>
<MessageFooter>
<MessageActions onCopy={copy} onRetry={retry} />
</MessageFooter>
</MessageContent>
</Message>;Composition
Message
├── MessageAvatar
└── MessageContent
├── MessageHeader
├── Bubble
│ └── BubbleContent
└── MessageFooter
└── MessageActionsMessageGroup stacks consecutive rows from one sender.
Author and timestamp
Message reads the author once and shares it with MessageHeader and
MessageAvatar, so a row cannot publish two different names. variant selects
the default author (You, Assistant, System) and the default side (end
for a user, start otherwise); author, timestamp and meta override what
the header paints. A timestamp is the caller's formatted string; no locale is
applied.
Data slots
Reasoning, tool calls, citations and the streaming marker are data, not markup: presence is the only switch, and a restored record's absent slot paints nothing. They sit outside the bubble, so a trace can be read without being painted into the reply.
Actions
MessageActions renders nothing when no handler can serve a control. Put it in
MessageFooter; the footer follows the row's side.
API
| Part | Renders | Props |
|---|---|---|
Message | role="group" named by label or the author | variant?, align?, author?, timestamp?, meta?, avatarSrc?, label?, className?, children |
MessageAvatar | the shipped Avatar at sm | name?, src?, className? |
MessageHeader | the author, timestamp and meta line | className? |
MessageContent | the slot column between avatar and edge | reasoning?, toolCalls?, sources?, streaming?, streamingLabel?, reasoningLabel?, sourcesLabel?, className?, style?, children |
MessageFooter | the under-bubble line, on the row's side | className?, children |
MessageActions | a named action row, or null | onCopy?, onRetry?, onFeedback?, copyLabel?, retryLabel?, helpfulLabel?, notHelpfulLabel?, className? |
MessageGroup | consecutive rows, tighter | gap?, className?, children |
Breaking change
MessageRoot is now Message. The old root built the bubble itself; the new
one does not. Every row must place a Bubble inside MessageContent, and
actions move from MessageContent to MessageFooter. This is the shadcn
structure the component is modelled on; the split is what lets a bubble be
quoted, grouped or reused on its own.
Edit this page on GitHubpackages/components/docs/message.mdx