Skip to content

Pro pre-sale is open. Installation opens January 2027. See Pro

Components

MessageActions

The toolbar a message shows on hover, focus or a tap: react, reply, forward, pin and select, then your own controls such as a More menu. React opens the quick reactions and the full EmojiPicker. Pass it to MessageBubble as toolbar.

  • npm: 1 direct, 6 total
  • registry: 6 direct, 7 total

Preview

Props

  • pinned
  • react
  • reply
  • forward
  • pin
  • select
  • more
import { MessageActions } from "@/components/ui/message-actions"

const addReaction = (emoji: string) => { /* Add the reader's reaction to the message. */ }
const replyToMessage = () => { /* Put the message in the Composer's replyTo. */ }
const forwardMessage = () => { /* Open the forward dialog. */ }
const setPinned = (pinned: boolean) => { /* Pin or unpin the message. */ }
const startSelection = () => { /* Turn on selection mode with this message picked. */ }

<MessageActions onReact={addReaction} onReply={replyToMessage} onForward={forwardMessage} onPinnedChange={setPinned} onSelect={startSelection} />

Usage

When to use

  • Offer the actions on one message (react, reply, forward, pin, select) when the reader points at it, focuses it or taps it.
  • Add a More menu with the rarer actions, such as Copy, Edit and Delete.

When not to use

  • Actions on many picked messages at once. Use MessageList selection with a bar of Buttons.
  • A toolbar that is always shown above a document. Use ToggleGroup or Buttons in a row.
  • A right-click menu on any item. Use ContextMenu.

Examples

Toolbar

import { MessageActions } from "@/components/ui/message-actions"

const addReaction = (emoji: string) => { /* Add the reader's reaction to the message. */ }
const replyToMessage = () => { /* Put the message in the Composer's replyTo. */ }
const forwardMessage = () => { /* Open the forward dialog. */ }
const setPinned = (pinned: boolean) => { /* Pin or unpin the message. */ }
const startSelection = () => { /* Turn on selection mode with this message picked. */ }

<MessageActions onReact={addReaction} onReply={replyToMessage} onForward={forwardMessage} onPinnedChange={setPinned} onSelect={startSelection} />

Pinned message

Pin stays pressed, with a filled icon, while the message is pinned.

import { MessageActions } from "@/components/ui/message-actions"

const addReaction = (emoji: string) => { /* Add the reader's reaction to the message. */ }
const replyToMessage = () => { /* Put the message in the Composer's replyTo. */ }
const setPinned = (pinned: boolean) => { /* Pin or unpin the message. */ }

<MessageActions pinned onReact={addReaction} onReply={replyToMessage} onPinnedChange={setPinned} />

With a More menu

Your own controls follow the built-in ones.

import { IconCopy, IconDots, IconPencil, IconTrash } from "@tabler/icons-react"
import { Button } from "@/components/ui/button"
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
import { MessageActions } from "@/components/ui/message-actions"

const addReaction = (emoji: string) => { /* Add the reader's reaction to the message. */ }
const replyToMessage = () => { /* Put the message in the Composer's replyTo. */ }

<MessageActions onReact={addReaction} onReply={replyToMessage}>
  <DropdownMenu>
    <DropdownMenuTrigger asChild>
      <Button size="sm" variant="ghost" iconOnly aria-label="More actions">
        <IconDots size={16} />
      </Button>
    </DropdownMenuTrigger>
    <DropdownMenuContent align="end">
      <DropdownMenuItem leadingIcon={<IconCopy size={16} />}>Copy text</DropdownMenuItem>
      <DropdownMenuItem leadingIcon={<IconPencil size={16} />}>Edit</DropdownMenuItem>
      <DropdownMenuItem destructive leadingIcon={<IconTrash size={16} />}>Delete</DropdownMenuItem>
    </DropdownMenuContent>
  </DropdownMenu>
</MessageActions>

Do and don't

Do

  • Pass only the handlers the conversation supports; each one shows its button.
  • Put rare or destructive actions (Copy, Edit, Delete) in a DropdownMenu as children.
  • Keep the quick reactions to six or fewer.

Don't

  • Do not put text buttons in the toolbar; it holds icon buttons only.
  • Do not show the toolbar on every message at once; MessageBubble shows it on hover, focus or a tap.
  • Do not add a second reaction row under the toolbar; reactions show on the message.

Props

NameTypeValuesDefaultDescription
onReact(emoji: string) => void--Shows React, and is called with the emoji picked.
quickReactionsstring[]--The reactions offered first. Default 👍 ❤️ 😂 😮 😢 🙏.
frequentEmojistring[]--Emoji shown first in the full picker. Default: the quick reactions.
onReply() => void--Shows Reply. Put the message in the Composer's replyTo.
onForward() => void--Shows Forward.
pinnedboolean-falseWhether the message is pinned. Pin is pressed while it is.
onPinnedChange(pinned: boolean) => void--Shows Pin, and is called with the new pinned state.
onSelect() => void--Shows Select. Turn on selection mode in the MessageList with this message picked.
labels{ toolbar?: string; react?: string; reply?: string; forward?: string; pin?: string; select?: string; moreReactions?: string; quickReactions?: string; emojiPicker?: EmojiPickerLabels }--Names of the toolbar and its buttons. Defaults: Message actions, React, Reply, Forward, Pin, Select, More reactions, Quick reactions. Vietnamese: Thao tác tin nhắn, Bày tỏ cảm xúc, Trả lời, Chuyển tiếp, Ghim, Chọn, Thêm cảm xúc, Cảm xúc nhanh.
childrennode--More controls after the built-in ones, such as a DropdownMenu with Copy, Edit and Delete. Use ghost icon Buttons in size sm.

Anatomy

PartDescription
rootThe toolbar: the raised fill with a ring and shadow-med, 4px of padding.
actionEach built-in action: a ghost icon Button in size sm with a Tooltip. React, Reply, Forward, Pin and Select, in that order.
popoverThe Popover React opens above the toolbar.
quickThe row of quick reactions with a More reactions button.
reactionOne quick reaction button, the size of an md control, at the xl text size.
pickerThe EmojiPicker shown after More reactions.

Accessibility

Role: toolbar

KeysAction
TabMoves into the toolbar, to the button that last had focus. The toolbar is one tab stop.
Arrow Left / Arrow RightMoves to the previous or next button, wrapping around. Follows right-to-left.
Home / EndMoves to the first or the last button.
Enter / SpaceRuns the focused action. On React, opens the reactions.
EscapeCloses the reactions and returns focus to React.
  • A toolbar named by labels.toolbar (EN `Message actions`, VI `Thao tác tin nhắn`).
  • Every button is an icon button named by its action and shows the same words in a Tooltip.
  • Pin is a toggle button: aria-pressed is true while the message is pinned, and its icon turns filled.
  • The quick reactions are buttons named by the emoji's name, such as EN `thumbs up`. More reactions opens the EmojiPicker and focuses its search.
  • Only actions with a handler show, so the toolbar never offers something that does nothing.

Install

npx werewolf-ui add message-actions

Run npx werewolf-ui init once in your app before adding components. Init sets up tokens, their CSS import and agent rules. Add installs this component and its dependencies while preserving your theme. See Installation for requirements and the alternative registry setup.