iMessage UIv0.1.0

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.

Riley
hold this bubble down
"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

PropTypeDefaultDescription
open*booleanWhether the picker is shown.
onPick*(kind: ReactionKind) => voidCalled with the chosen reaction.
onClose*() => voidDismissal (scrim tap / Escape).
side"left" | "right""left"Which side of the bubble it anchors to.
currentReactionKind | nullThe 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.