Message Actions
Controls attached to a message: copy, regenerate, rate. They sit under the content and reveal on hover, so a transcript stays uncluttered at rest.
Plain-text replies
An assistant reply usually reads better as plain prose than as a bubble: full width, no
tinted background, and selectable text. Bubble variant="ghost" drops the background,
border, radius, and padding, and keeps the alignment and spacing.
The person's turn stays a bubble; the reply does not:
{/* the person asks — a bubble, right-aligned */}
<Message align="end">
<MessageAvatar>
<Avatar>
<AvatarFallback>JD</AvatarFallback>
</Avatar>
</MessageAvatar>
<MessageContent>
<Bubble variant="default" align="end">
<BubbleContent>How much storage do I have?</BubbleContent>
</Bubble>
</MessageContent>
</Message>
{/* the reply — plain prose, full width, copyable */}
<Message align="start">
<MessageContent>
<Bubble variant="ghost">
<BubbleContent className="text-base">{reply}</BubbleContent>
</Bubble>
<MessageActions>
<MessageCopyButton value={reply} />
</MessageActions>
</MessageContent>
</Message>Drop MessageAvatar from the reply so the prose starts at the same left edge as
everything else, and set BubbleContent to text-base. The bubble default of
text-sm is tuned for short turns, not for long answers.
MessageHeader and MessageFooter drop their horizontal padding next to a ghost
bubble, so metadata lines up with the prose.
Rich replies
BubbleContent styles none of its children. Render the answer with the typography
components.
<Bubble variant="ghost">
<BubbleContent className="flex flex-col gap-3 text-base">
<P>Here are the three steps:</P>
<OrderedList>
<li>Open a ticket.</li>
<li>Include your project ID.</li>
</OrderedList>
<Code>quota --project 4711</Code>
</BubbleContent>
</Bubble>Markdown
The design system takes no markdown dependency, so the renderer and its plugins are your choice.
Tailwind's reset flattens h1–h6, ul, ol, and p, so a renderer's raw HTML
lands in BubbleContent with headings at body size and lists with no markers. Map the
nodes to the typography components instead.
<Bubble variant="ghost">
<BubbleContent className="flex flex-col gap-3 text-base">
<Markdown
components={{
p: P,
h3: H3,
ul: List,
ol: OrderedList,
code: Code,
a: Link,
blockquote: Blockquote,
}}
>
{reply}
</Markdown>
</BubbleContent>
</Bubble>Reveal behaviour
Actions fade in when the message is hovered or when anything inside it takes keyboard focus, so they are reachable by tab.
The reveal is gated on pointer-fine. Touch devices have no hover, so the actions are
always visible there.
To pin them open, override the resting opacity:
<MessageActions className="pointer-fine:opacity-100">More than copy
MessageActions is an open row. Anything in it lines up with the copy button. Use
variant="ghost" size="icon-sm" to match, and give every icon-only control a visually
hidden label.
Copy behaviour
MessageCopyButton writes value to the clipboard, swaps to a tick for two seconds,
then returns. The icons crossfade.
The clipboard needs a secure context and the user can refuse permission. When the write fails the button stays in its resting state instead of confirming a copy that did not happen.
<MessageCopyButton value={reply} copyLabel="Copy answer" copiedLabel="Answer copied" />The accessible name switches between the two labels, which announces the result to a screen reader.
Components
| Component | Description |
|---|---|
MessageActions | Hover-revealed row of controls under a message |
MessageCopyButton | Copies a string, confirming with a tick |
Props
MessageActions
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | - | Additional CSS classes |
| ...props | React.HTMLAttributes | - | Native div props |
MessageCopyButton
| Prop | Type | Default | Description |
|---|---|---|---|
| value | string | — | Text written to the clipboard |
| copyLabel | string | Copy message | Hidden label at rest |
| copiedLabel | string | Copied | Hidden label after a successful copy |
| variant | Button variant | ghost | Passed through to Button |
| size | Button size | icon-sm | Passed through to Button |
Related
Message and Bubble build the turn. Chat shows the plain-reply style in a full interface.