Message actions

The row under an assistant reply: copy, regenerate, thumbs up or down with a short feedback form, and share.

Loading preview…

Install

  1. 1

    Run this command in your Tailwind CSS project. It adds components/ai/message-actions.tsx and installs its dependencies.

    $ npx shadcn@latest add https://gradientdeck.com/ai-components/r/message-actions.json

Usage & API

Example

message-actions.tsx
import { MessageActions } from "@/components/ai/message-actions"

<MessageActions
  text="…"
/>

Props MessageActions

PropType & descriptionRequired
textstringRequired
onRegenerate() => voidOptional
onFeedback(feedback: Feedback) => voidOptional
onShare() => voidOptional
regeneratingbooleanOptional
persistentbooleanKeep the toolbar visible instead of only on hover or focus.Optional
defaultFeedbackOpenbooleanStart with thumbs-down picked and the feedback form open.Optional

Dependencies

  • React
  • Tailwind CSS
  • motion
  • lucide-react
Implementation notes

Default exports MessageActions and the Feedback type. The five buttons are Copy or Copied, Regenerate, Good response, Bad response, and Share or Link copied. aria-pressed reflects the current vote on the two thumb buttons. The dialog lists the four reasons and the buttons Skip and Send. Send does not require a reason. An empty selection still calls onFeedback with an empty reasons array. A pointer outside the dialog and outside the thumbs-down button closes the form without sending. The thanks string and the copied strings share one live slot, so only one of them shows. The demo keeps two reply strings, fades the paragraph while regenerating is true, and swaps the index after 1.2 seconds. That swap is the demo's timer, not a prop. Pass the next text in, and pass regenerating while you wait.

Choosing a layout

Put the row under a finished assistant Message, not under the person's bubble. text should be the plain string you want on the clipboard, even if the visible reply has formatting. If the reply is still streaming, Streaming text's caret variant already has Stop, Continue, Skip to end, Copy and Regenerate. Use those controls while the caret is moving, and this row once state is done, or you will show two copies of Regenerate. Branch picker is the other control that sits under a reply. It steps versions. It does not copy or vote. They can sit on the same turn if you lay them out.

Leave persistent off in a long thread so the actions appear on the message people are pointing at. The class uses group-hover, so an ancestor needs the group class, which is what the demo puts on the wrapper. Turn persistent on in a single-reply panel where there is no hover to discover. defaultFeedbackOpen is for a preview or a test. It starts the vote at down and opens the form, and it does not move focus into the form. Focus moves in only after a person opens it, so a page load does not steal the caret from the composer.

Share says the link was copied, but nothing is on the clipboard.

The share button never calls the clipboard. Copy is the only button that writes text, and it writes the text prop, not a URL. Share calls onShare and then shows Link copied for 1.6 seconds. Put the real copy-link work in onShare if you want the clipboard to change. If the clipboard write in Copy fails, the catch is empty and the label still flips to Copied, so a denied permission looks the same as a success.

I passed a note in the feedback type and it never appears in the form.

The form has no note field. Send reports vote down and whatever reasons are pressed, chosen from the four fixed strings. The note property exists on the Feedback type so a caller can attach one, but the component will not ask for it. Skip does not emit a second callback. If the person had already set the vote to down by opening the form, that vote stays until they press the thumb again, which sends null.