Reactions
Emoji reactions under a message, a comment or a post. Each pill is a toggle with a count, and the add button opens the emoji picker. A soft halo pulses on the one you just added.
The new onboarding flow is live for everyone. Signups are up 18% since this morning.
Installation#
Usage#
The preview is yours to explore. Its code comes with a Pro license, paid once and used in every project.
Have a license? Unlock it here
Anatomy#
import { Reaction, ReactionAdd, Reactions } from "@/components/ui/reactions"
<Reactions>
<Reaction emoji="👍" count={3} />
<ReactionAdd />
</Reactions>Yours#
A pressed pill is a reaction you made: it takes a stronger fill, a hairline ring and full ink. Clicking it again takes yours away, and a reaction nobody holds any more leaves the row. The tooltip names who reacted, you included, and never opens on touch.
The ring pulse#
Turning a reaction on plays a soft halo in the reading ink as the hit of the press: it leaves the pill’s edge and keeps travelling outward while it dissolves, about half a second in all. It is ink, not the accent, like the pressed pill it marks. The pill itself stays put. Only one pill pulses at a time. With reduced motion the halo appears in place and fades.
Adding from the picker#
ReactionAdd opens the Emoji Picker above the row. Picking an emoji calls onEmojiSelect; add yourself to that reaction and its pill pulses as it arrives, whether it is new or already in the row.
Sizes#
Without a count#
Leave count unset for a round, emoji-only pill, as in a quick-reaction bar. Give each one a label.
In a chat thread#
API reference#
Reactions
The row, a div with role="group" named "Reactions". Owns the size and the one ring pulse the row plays at a time. Accepts className, merged last.
| Prop | Type | Default | Description |
|---|---|---|---|
| size | "sm" | "md" | - | Pill size: sm is 24px tall, md 28px. Follows the density when unset: compact gives sm, otherwise md. |
| children | ReactNode | - | Reaction pills, then a ReactionAdd at the end. |
| Data attribute | Description |
|---|---|
| data-slot | reactions on the row. |
Reaction
One reaction pill, built on Toggle (Radix). Pressed means the reaction is yours. Turning it on sends a soft ink halo off its edge like a wave. Accepts className, merged last.
| Prop | Type | Default | Description |
|---|---|---|---|
| emoji* | string | - | The emoji. Also the key the row uses to pulse the pill a picked emoji lands on. |
| count | number | - | How many people reacted. Rolls when it changes. Leave unset for an emoji-only round pill. |
| tooltip | ReactNode | - | Who reacted, shown on hover ("Ana, Marc and you"). Never opens on touch. |
| label | string | "👍, 3 reactions" | Accessible name. Built from emoji and count by default; aria-pressed says whether it is yours. |
| pressed | boolean | - | Controlled state: whether the current person reacted with this emoji. |
| defaultPressed | boolean | false | Initial state when uncontrolled. |
| onPressedChange | (pressed: boolean) => void | - | Called when the person adds or removes their reaction. |
| disabled | boolean | false | Shows the reaction without letting anyone toggle it. |
| Data attribute | Description |
|---|---|
| data-state | on when the reaction is yours, off otherwise. |
| data-pulsing | Present while the ring pulses, for about half a second after the reaction is turned on or picked. |
| data-bare | true when there is no count, which makes the pill round. |
| data-slot | reaction on the pill, reaction-emoji and reaction-count inside it, reaction-pulse on the halo layer. |
ReactionAdd
The add-reaction button at the end of the row. Opens the Emoji Picker in a popover; the same ink halo as the pulse grows in and holds for as long as the picker is open. Accepts className, merged last.
| Prop | Type | Default | Description |
|---|---|---|---|
| onEmojiSelect | (emoji: string, datum: EmojiDatum) => void | - | Receives the emoji the person chose; its pill in the row pulses when it lands. |
| label | string | "Add reaction" | Accessible name and tooltip. |
| icon | ReactNode | <Smiley /> | Replaces the smiley glyph. |
| side | "top" | "right" | "bottom" | "left" | "top" | Preferred side of the picker; it flips when there is no room. |
| align | "start" | "center" | "end" | "start" | Alignment of the picker against the button. |
| presets | string[] | - | The picker's frequently used row. |
| recent | string[] | - | Controlled recently used emoji. |
| storageKey | string | null | "koala-emoji-recent" | Where the picker remembers recent emoji. null keeps them in memory only. |
| container | HTMLElement | null | document.body | Element the picker popover portals into. |
| Data attribute | Description |
|---|---|
| data-state | open or closed, from the picker trigger. |
| data-bare | Always true: the button is round. |
| data-slot | reaction-add on the button, reaction-ring on the ring while the picker is open. |