Row of emoji-reaction pills with counts, plus a + picker. Tap = spring-pop.
Anatomy
♥12 🔥8 🧠4 + ← pills h-44 hit area
└┬┘ └ opens picker popover
└ mine: bg-brand/10, count text-brand
┌ ♥ 🔥 👀 🧠 😅 📈 ┐ ← picker, surface-bright
└──────────────────┘Props
| Prop | Type | Default | Notes |
|---|---|---|---|
reactions | Reaction[] | — | { emoji, count, mine? }. Counts/mine are owned by the caller. |
onReact | (emoji: string) => void | — | Fired on pill tap or picker choice. |
pickerEmojis | string[] | ♥ 🔥 👀 🧠 😅 📈 | Emoji offered by the + picker. |
className | string | — | Merged via cn. |
Motion
--motion-spring (250ms, overshoot) scales the emoji 1 → 1.3 → 1 on tap — the budgeted-playfulness moment (DESIGN.md → Identity). Cleared on animationend so repeated taps replay. Reduced-motion: the global reset collapses the animation.
A11y
- · Pills expose
aria-pressed(own-reaction);+exposesaria-label+aria-expanded. - · Pill hit area 44px; picker items 40px;
+is 44×44. - · Picker is a Radix Popover: focus moves into the panel on open, Escape and outside-click dismiss, and focus returns to the
+trigger on close. Each option is a labelled button (React ♥).