Components
TapbackPicker
The floating reaction row that springs up on a long-press. Controlled: you own `open` and wire the gesture (useLongPress pairs perfectly). Dismisses on scrim tap or Escape.
"use client";
import { useState } from "react";
import {
ChatBubble,
IMessageScreen,
TapbackPicker,
useLongPress,
} from "imessage-ui";
import type { Reaction, ReactionKind } from "imessage-ui";
/** Long-press (or right-click / double-click) the bubble to react. */
export default function TapbackDemo() {
const [open, setOpen] = useState(false);
const [reaction, setReaction] = useState<ReactionKind | null>("heart");
const longPress = useLongPress(() => setOpen(true));
const reactions: Reaction[] = reaction ? [{ kind: reaction, by: "you" }] : [];
return (
<IMessageScreen className="w-full max-w-sm rounded-2xl p-4 pt-10">
<div className="relative" {...longPress}>
<ChatBubble
variant="received"
text="hold this bubble down"
senderName="Riley"
avatar={{ initial: "R", gradient: ["#FF9FB2", "#E0506E"] }}
reactions={reactions}
/>
<TapbackPicker
open={open}
current={reaction}
onPick={kind => {
setReaction(prev => (prev === kind ? null : kind));
setOpen(false);
}}
onClose={() => setOpen(false)}
/>
</div>
</IMessageScreen>
);
}Usage
import { TapbackPicker, useLongPress } from "imessage-ui";
const lp = useLongPress(() => setOpen(true));
<div className="relative" {...lp}>
<ChatBubble … />
<TapbackPicker open={open} current={reaction} onPick={setReaction} onClose={() => setOpen(false)} />
</div>API reference
| Prop | Type | Default | Description |
|---|---|---|---|
| open* | boolean | — | Whether the picker is shown. |
| onPick* | (kind: ReactionKind) => void | — | Called with the chosen reaction. |
| onClose* | () => void | — | Dismissal (scrim tap / Escape). |
| side | "left" | "right" | "left" | Which side of the bubble it anchors to. |
| current | ReactionKind | null | — | The applied reaction, highlighted in the row. |
Accessibility
Renders as a menu of menuitemradio buttons with aria-checked on the current reaction; Escape and the scrim both close it.