Skip to content
Component installs need the registry setup
VegaStack Design

Reactions

Slack-style emoji reactions — pills with counts that toggle, a card naming who reacted, and an add button with quick reactions.

Status
stable
Since
0.23.37
Accessibility pattern
toggle buttons with aria-pressed, labelled group

Last updated

Install

Add Reactions from the VegaStack registry. The CLI verifies the item's integrity hash before writing it.

pnpm dlx shadcn@latest add @vegastack/reactions

The same command installs the registry items it composes: @vegastack/button, @vegastack/emoji-picker, @vegastack/hover-card.

Usage

import { Reactions, toggleReaction } from "@/components/ui/reactions";

<Reactions
  reactions={reactions}
  onToggle={(emoji) => setReactions((rs) => toggleReaction(rs, emoji, me))}
/>;

Each pill shows an emoji and its count. It is tinted and pressed when the viewer reacted, and a click toggles it. Hover a pill to see who reacted, for example "Neha Kapoor, Arjun Mehta and 3 others reacted with :thumbs_up:". People who are no longer active are marked "(Inactive)".

The ghost "Add reaction" button opens the compact EmojiPicker with a quick row: 👍 ❤️ 😄 🎉 👀 🙏. Picking an emoji you already reacted with does nothing.

The host owns the data. onToggle may return a promise: the pill stays busy until it settles, and repeat clicks are ignored meanwhile. toggleReaction is the pure update for an optimistic change; on failure, restore the previous list and show a toast.

Anatomy

Reactions — data-slot="reactions"
ReactionAdd — data-slot="reaction-add"

Examples

No reactions yet

Read-only

Without onToggle there is no add button, and pills only show who reacted.

Fewer names

maxUsersShown caps the names in the card; the rest become "and N others".

Optimistic with rollback

The pill flips at once and stays busy until the promise settles. Here 👀 fails and rolls back.

Add button alone

ReactionAdd is the button CommentItem puts in its hover actions.

Add a reaction

In comments

CommentItem and CommentList take onReactionToggle and read each comment's reactions; see Comments.

API Reference

PropTypeDefaultDescription
reactions*ReactionData[]—The reactions, in display order. Zero-count entries are skipped.
classNamestring—Classes for the row.
maxUsersShownnumber10Names listed in a pill's card before "and N others".
onToggleMaybeAsync<[emoji: string]>—Add or remove the viewer's reaction; may return a promise.
quickReactionsstring[]QUICK_REACTIONSThe picker's quick row.
showAddbooleantrueShow the "Add reaction" button (needs onToggle).

Data attributes and CSS variables on Reactions

AttributeValues
data-slot"reactions"
PropTypeDefaultDescription
count*number—How many people reacted with it.
emoji*string—The emoji character.
reacted*boolean—The viewer is one of them — the pill is tinted and pressed.
users*ReactionUser[]—Who reacted, oldest first (may be fewer than count).
PropTypeDefaultDescription
id*string—Stable id.
name*string—Display name.
inactivebooleanfalseNo longer active — the name is marked "(Inactive)".

Accessibility

  • The row is a group named "Reactions".
  • Each pill is a toggle button with aria-pressed, named with the emoji and count, for example "thumbs up 5, react".
  • A pill waiting on its promise sets aria-busy.
  • The add button is named "Add reaction". The picker's quick row, category bar and grid each take one tab stop and move with the arrow keys.
KeyAction
Enter / SpaceToggle a pill, or open the picker
← / →Move within the picker's rows and grid
EscClose the picker
ContractStates tested
Behaviourdefault, reacted, busy, hover-card, add
Accessibilitylabeled, pressed, browser-accessibility-test
Visualdefault, reacted

Do / Don't

Do
Update optimistically with toggleReaction, and roll back with a toast when the request fails.
Don't
Use reactions as a vote count or rating — they are lightweight acknowledgements.

On this page