v1.5

Chat Message Actions

Icon action row under a chat message for copying, rating, regenerating and opening more options.

Pro

Your deploy failed because the build step ran out of memory. Raise the Node heap to 4 GB in the build settings and redeploy.

Description

ChatMessageActions is a compound component that renders a horizontal <div> of icon-only buttons. It ships presets for the actions almost every AI chat needs: Copy writes to the clipboard and swaps to a check, ThumbsUp and ThumbsDown are toggle buttons that can share one rating through Feedback, Regenerate reruns a response and Menu opens an overflow dropdown. Action is the bare button for anything else.

Put it under an assistant response or a user bubble. By default the row sits at 75% opacity and comes up to full strength when the message is hovered, focused, or has an open menu, so a long conversation does not turn into a wall of icons.

Don't use it for a toolbar with labelled buttons or form controls; use Button in a flex row, or Button Group when the buttons are visually joined. For a single standalone copy button outside a chat, a Button with your own clipboard handler is lighter. For suggested follow-up prompts, use Prompt Suggestion.

ChatMessageActions is a Create UI Pro component. Install it with a Pro seat.

Installation

pnpm dlx @create-ui/cli add chat-message-actions

Anatomy

<ChatMessageActions>
  <ChatMessageActions.Copy />
  <ChatMessageActions.Feedback>
    <ChatMessageActions.ThumbsUp />
    <ChatMessageActions.ThumbsDown />
  </ChatMessageActions.Feedback>
  <ChatMessageActions.Regenerate />
  <ChatMessageActions.Menu />
  <ChatMessageActions.Action />
</ChatMessageActions>

Usage

import { ChatMessageActions } from "@/components/ui/chat-message-actions"
<ChatMessageActions>
  <ChatMessageActions.Copy value={message.text} />
  <ChatMessageActions.Regenerate onClick={regenerate} />
</ChatMessageActions>

Examples

Every example is a Pro preview (marked with a Pro badge).

Presets

Each preset brings its icon and an English accessible name ("Copy message", "Good response", "Bad response", "Regenerate response", "More actions"). Pass aria-label to translate or make it more specific.

Pro

Sizes

size is set per action and takes sm (24px, the default), md (32px) or lg (40px). The glyph follows the button, so the copy check and the filled thumbs scale with it. There is no xs, because 20px is under the minimum touch target.

Pro

Appearance

Actions are Buttons with variant="neutral-light" and appearance="ghost" by default. outline and soft work when the row sits on a busy surface and needs visible hit areas.

Pro

Copy

Give Copy a value (or a function that returns one) and it writes the text with the Clipboard API. The check only appears after the write succeeds, stays for two seconds, and "Copied" is announced to screen readers. onCopy and onCopyError report the result.

Pro
pnpm add @tanstack/react-query

Not copied yet

Pass copied to drive the check yourself, for example when the copy happens somewhere else. In controlled mode the component never resets it.

Feedback

Wrap the thumbs in Feedback to keep one rating: value is "up", "down" or null, and pressing the active thumb again clears it. Pressed thumbs switch to the filled glyph and set aria-pressed.

Pro

Was this answer helpful?

Outside a Feedback group each thumb is an independent toggle with pressed, defaultPressed and onPressedChange.

Custom actions

Action takes any icon and a required aria-label. Presets also accept a child icon, and the thumbs take pressedIcon, so a thumb can become a bookmark toggle without writing the pressed logic again.

Pro

Overflow menu

Menu is a plain action, so it drops straight into Dropdown as the trigger. While the menu is open the row stays at full opacity even though focus has moved into the popover.

Pro

Hover reveal

Inside ChatMessage.Assistant or ChatMessage.User, reveal="hover" (the default) keys off the whole message row, not just the action strip. Hover the message or tab into it to bring the row up. Use reveal="always" for the latest message or on touch-first layouts.

Pro
Use a composite index on (user_id, created_at) so the dashboard query can skip the sort.

Accessibility

KeyDescription
TabMoves focus to the next action. Each action is its own tab stop.
Shift + TabMoves focus to the previous action.
Enter / SpaceActivates the focused action: copies, toggles a thumb, or opens menu.
EscapeCloses an open tooltip or overflow menu.

ARIA notes:

  • Every action renders a native <button>. Action requires aria-label; presets ship a default name you can override.
  • Copy switches its accessible name to copiedLabel while the check shows, and a visually hidden role="status" region next to it announces the same text.
  • ThumbsUp and ThumbsDown set aria-pressed. Feedback renders role="group" with the label "Rate this response"; pass aria-label to change it.
  • Preset glyphs are aria-hidden, so only the button name is read. Icons you pass to Action are covered by its aria-label too.
  • reveal="hover" only lowers opacity. Actions stay in the tab order and the accessibility tree, and the row reaches full opacity on :focus-within.
  • The icon cross-fade drops its scale and blur under prefers-reduced-motion and keeps only a short opacity fade.
  • tooltip uses the Radix Tooltip, which opens on focus as well as hover. See the Radix Tooltip docs.

Styling

Tailwind override: pass className to the root for spacing and to any action to restyle one button.

<ChatMessageActions className="gap-1">
  <ChatMessageActions.ThumbsUp className="aria-pressed:text-primary-base" />
</ChatMessageActions>

Data slots and attributes: the component sets these for CSS targeting:

  • data-slot="chat-message-actions" on the root, with data-reveal ("hover" or "always").
  • data-slot="chat-message-action" on every button, with the Button attributes data-variant, data-appearance and data-size.
  • data-action on each preset: "copy", "thumbs-up", "thumbs-down", "regenerate" or "menu".
  • data-copied on Copy while the check shows, and data-pressed plus aria-pressed="true" on a pressed thumb.
  • data-slot="chat-message-action-icon" on the glyph wrapper of Copy and the thumbs, with data-state ("idle" / "copied" for copy, "off" / "on" for thumbs).
  • data-slot="chat-message-action-status" on the hidden live region next to Copy.
  • data-slot="chat-message-actions-feedback" on the Feedback group, with data-value ("up", "down" or "none").
[data-slot="chat-message-action"][data-action="thumbs-down"][data-pressed] {
  /* ... */
}
  • Chat Message: the message layout these actions sit under. It marks the row that reveal="hover" listens to.
  • Button: use it for labelled or standalone actions outside a message.
  • Dropdown Menu: pairs with Menu for actions that do not deserve their own icon.
  • Tooltip: what the tooltip prop renders. Reach for it directly if you need a different variant or side.

API Reference

ChatMessageActions

The row. Extends React.ComponentProps<"div">. Also available as ChatMessageActions.Root and ChatMessageActionsRoot.

Props

PropTypeDefaultDescription
classNamestring-Tailwind classes merged via cn().
childrenReact.ReactNode-Actions, Feedback groups and Dropdowns.

Variants

VariantOptionsDefaultDescription
reveal"hover" "always""hover"hover rests at 75% opacity until the message is hovered or focused. always does not dim.

ChatMessageActions.Action

An icon-only action. Renders Button with iconOnly, so it extends the Button props (disabled, loading, asChild, onClick and every native button attribute) except iconOnly. Also exported as ChatMessageAction.

Props

PropTypeDefaultDescription
aria-labelstring-Required. The accessible name of the icon button.
tooltipReact.ReactNode-Wraps the button in a Tooltip with this content.
loadingbooleanfalseSwaps the icon for a spinner and sets aria-busy.
disabledbooleanfalseDisables the button.
classNamestring-Tailwind classes merged via cn().
childrenReact.ReactNode-The icon.

Variants

VariantOptionsDefaultDescription
size"sm" "md" "lg""sm"24px, 32px or 40px square.
variant"primary" "neutral-solid" "neutral-light" "danger" "success" "inverse-solid" "inverse-light""neutral-light"Color intent, passed to Button.
appearance"solid" "outline" "soft" "ghost""ghost"Fill weight, passed to Button.
shape"rounded" "pill" "square""rounded"Corner radius, passed to Button.

ChatMessageActions.Copy

Copies text to the clipboard and swaps its glyph to a check. Takes every Action prop, with aria-label optional (default "Copy message"). The native value and onCopy button attributes are replaced by the props below.

Props

PropTypeDefaultDescription
valuestring | (() => string)-Text to write. A function runs at click time. Without it, nothing is copied.
copiedboolean-Controlled copied state. When set, the component never resets it.
onCopy(text: string) => void-Fires after the clipboard write succeeds.
onCopyError(error: unknown) => void-Fires when value throws, the Clipboard API is missing, or the write fails.
copiedIconReact.ReactNodecheck iconGlyph shown while copied.
copiedLabelstring"Copied"Accessible name, announcement and tooltip text while copied.
classNamestring-Tailwind classes merged via cn().
childrenReact.ReactNodecopy iconReplaces the idle glyph.

Calling event.preventDefault() inside onClick skips the copy.

ChatMessageActions.Feedback

Holds one rating for the ThumbsUp and ThumbsDown inside it. Extends React.ComponentProps<"div"> and renders role="group".

Props

PropTypeDefaultDescription
value"up" | "down" | null-Controlled rating.
defaultValue"up" | "down" | nullnullInitial rating when uncontrolled.
onValueChange(value: "up" | "down" | null) => void-Fires on every change. Pressing the active thumb sends null.
aria-labelstring"Rate this response"Name of the group.
classNamestring-Tailwind classes merged via cn().
childrenReact.ReactNode-A ThumbsUp and a ThumbsDown.

ChatMessageActions.ThumbsUp / ChatMessageActions.ThumbsDown

Toggle buttons for rating a response. Take every Action prop, with aria-label optional (defaults "Good response" and "Bad response"). Inside Feedback the pressed state comes from the group and pressed / defaultPressed are ignored.

Props

PropTypeDefaultDescription
pressedboolean-Controlled pressed state.
defaultPressedbooleanfalseInitial pressed state when uncontrolled.
onPressedChange(pressed: boolean) => void-Fires with the new state on every toggle.
pressedIconReact.ReactNodefilled thumbGlyph shown while pressed.
classNamestring-Tailwind classes merged via cn().
childrenReact.ReactNodeoutline thumbReplaces the unpressed glyph.

ChatMessageActions.Regenerate / ChatMessageActions.Menu

Plain presets with a default glyph. Take every Action prop, with aria-label optional (defaults "Regenerate response" and "More actions"). Use loading on Regenerate while a new answer streams, and put Menu directly inside Dropdown as its trigger.

Props

PropTypeDefaultDescription
classNamestring-Tailwind classes merged via cn().
childrenReact.ReactNodepreset iconReplaces the default glyph.

Types

type ChatMessageActionSize = "sm" | "md" | "lg"
type ChatMessageFeedbackValue = "up" | "down" | null

chatMessageActionsVariants is the CVA recipe for the root, exported for composing your own row.