Message actions
AIThe row of quiet icon buttons under a message: copy, regenerate, rate with a reason, share and switch between versions.
npx prodular-ui add message-actionsOn a message
Shown on hover and focus, always on touch. Use visibility="always" for the latest answer.
Hover the question to see its copy action. The latest answer keeps its actions visible.
"use client"
import * as React from "react"import { RefreshCwIcon, Share2Icon } from "lucide-react"
import { AiMark } from "@/components/ui/ai-mark"import { Bubble, BubbleContent } from "@/components/ui/bubble"import { Message, MessageContent, MessageHeader } from "@/components/ui/message"import { MessageAction, MessageActionCopy, MessageActionFeedback, MessageActions, MessageVersions,} from "@/components/ui/message-actions"
const versions = [ "Net revenue retention for Q3 is 112%. Expansion from Halcyon Health and Orbis Retail added $186,000, and churn removed $74,000.", "NRR is 112% for Q3. Expansion added $186,000 (mostly Halcyon Health and Orbis Retail) against $74,000 of churn and contraction, up from 108% in Q2.",]
export default function MessageActionsHover() { const [version, setVersion] = React.useState(2) return ( <div className="flex w-full max-w-xl flex-col gap-8"> <Message from="user"> <MessageContent> <Bubble align="end"> <BubbleContent>What is our net revenue retention for Q3?</BubbleContent> </Bubble> <MessageActions className="justify-end"> <MessageActionCopy value="What is our net revenue retention for Q3?" /> </MessageActions> </MessageContent> </Message> <Message from="assistant"> <MessageContent className="gap-2"> <MessageHeader> <AiMark model="Generated by Claude Opus 5.5" /> Ledgerline Copilot </MessageHeader> <p>{versions[version - 1]}</p> <MessageActions visibility="always"> <MessageActionCopy value={versions[version - 1]} /> <MessageAction label="Regenerate"> <RefreshCwIcon /> </MessageAction> <MessageActionFeedback /> <MessageAction label="Share"> <Share2Icon /> </MessageAction> <MessageVersions value={version} total={versions.length} onValueChange={setVersion} /> </MessageActions> </MessageContent> </Message> <p className="text-[13px] text-muted-foreground">Hover the question to see its copy action. The latest answer keeps its actions visible.</p> </div> )}Feedback
Thumbs down opens a short form with reason chips and an optional comment.
Press thumbs down to open the feedback form
"use client"
import * as React from "react"import { RefreshCwIcon } from "lucide-react"
import { MessageAction, MessageActionCopy, MessageActionFeedback, MessageActions, type FeedbackValue,} from "@/components/ui/message-actions"
export default function MessageActionsFeedback() { const [value, setValue] = React.useState<FeedbackValue>(null) const [last, setLast] = React.useState<string>("") return ( <div className="flex flex-col items-center gap-4"> <MessageActions visibility="always"> <MessageActionCopy value="Q3 NRR is 112%." /> <MessageAction label="Regenerate"> <RefreshCwIcon /> </MessageAction> <MessageActionFeedback value={value} onValueChange={setValue} reasons={["Wrong numbers", "Missing accounts", "Out of date", "Too long"]} onFeedback={(feedback) => setLast(feedback.reasons.join(", ") || feedback.comment)} /> </MessageActions> <p className="text-[13px] text-muted-foreground tabular-nums" aria-live="polite"> {value === "up" && "Rated good"} {value === "down" && (last ? `Rated bad: ${last}` : "Rated bad")} {value === null && "Press thumbs down to open the feedback form"} </p> </div> )}Anatomy
<MessageActions> <MessageActionCopy value={markdown} /> <MessageAction label="Regenerate" onClick={regenerate}><RefreshCwIcon /></MessageAction> <MessageActionFeedback onValueChange={rate} onFeedback={sendFeedback} /> <MessageVersions value={2} total={3} onValueChange={setVersion} /></MessageActions>Guidance
Use it when
- Under every assistant message: copy, regenerate and feedback at least. Under user messages: copy and edit.
- MessageVersions when a message was regenerated or edited and older versions are kept.
Avoid
- Text buttons or filled buttons here. The actions should stay out of the way of the answer.
- More than six actions. Put the rest in a menu.
Rules for AI agents
Published in llms-full.txt, the registry and the MCP server, so coding agents follow them when they build with this component.
- Place MessageActions inside MessageContent, after Response and Sources, so hover follows the message.
- Set visibility="always" on the latest assistant message and leave the default on older ones.
- Give every MessageAction a verb first label. It is the tooltip and the accessible name.
- Pass the raw markdown of the answer to MessageActionCopy value, not the rendered text.
- Send ratings to your backend in onValueChange and the reasons in onFeedback. Keep reasons to five or fewer and specific to the product.
- Render MessageVersions only when total is 2 or more, and keep value one based.
API
MessageActions
| Prop | Type | Default | Description |
|---|---|---|---|
| visibility | "hover" | "always" | "hover" | Hover reveals on pointer devices and stays visible on touch. |
MessageAction
| Prop | Type | Default | Description |
|---|---|---|---|
| label | string | Tooltip and aria-label. |
MessageActionCopy
| Prop | Type | Default | Description |
|---|---|---|---|
| value | string | Copied to the clipboard. Shows a check for two seconds. |
MessageActionFeedback
| Prop | Type | Default | Description |
|---|---|---|---|
| value / onValueChange | "up" | "down" | null | Current rating. | |
| onFeedback | ({ reasons, comment }) => void | Called when the thumbs down form is sent. | |
| reasons | string[] | Reason chips in the form. |
MessageVersions
| Prop | Type | Default | Description |
|---|---|---|---|
| value / total / onValueChange | number | One based version switcher, 2 of 3. |
Installation
The Prodular UI CLI adds the source to components/ui with everything it depends on. In a project that already uses shadcn/ui, the shadcn CLI installs it from the Prodular registry too. See Installation for the first setup.
npx prodular-ui add message-actions# or, with the registry in components.jsonnpx shadcn@latest add @prodular/message-actions