# Reaction picker

> Long-press a message and a pill of emoji springs out of it one by one; slide the finger across and the emoji under it swells dock-style while its neighbours lean in; let go to pick and the emoji flies in an arc onto the message, landing in a badge with a pop.

- Type: Component (overlays)
- Access: Free, open source (MIT)
- Page: https://goodui.dev/native/components/reaction-picker
- Markdown: https://goodui.dev/native/components/reaction-picker.md
- Registry item: https://goodui.dev/native/r/reaction-picker.json
- Dependencies: react-native-gesture-handler, react-native-reanimated
- Keywords: react native emoji reactions, message reactions, long press reaction picker, facebook reactions, imessage tapback, slack reactions, emoji tray, expo reactions

## When to use

- Reacting to messages in a chat (ChatBubble has it built in through reaction and onReactionChange).
- Quick feedback on posts, comments, photos or team updates where one emoji is the whole reply.

## When not to use

- Actions such as reply, copy or delete: use context-menu (it can sit around the same element for a different gesture, or offer reactions as items).
- A single like: use like-button.
- Choosing from many emoji or searching: open a bottom-sheet with a grid.

## Installation

With the shadcn CLI (needs a components.json in the Expo app), then `npx expo install --fix` so native dependencies match your Expo SDK. The foundation comes along the first time.

```bash
npx shadcn@latest add https://goodui.dev/native/r/reaction-picker.json
npx expo install --fix
```

The component lands in `components/goodui-native/reaction-picker/` and imports the foundation from `components/goodui-native/foundation/` with relative paths; it also installs locale.

Built-in strings follow `<GooduiLocaleProvider>` (one [language pack](https://goodui.dev/native/r/locale-tr.json) translates the whole kit); the `labels` prop overrides them for one instance.

## Usage

```tsx
import { useState } from "react";
import { Text, View } from "react-native";
import { ReactionPicker } from "@/components/goodui-native/reaction-picker/reaction-picker";

export function Update() {
  const [reaction, setReaction] = useState<string | null>(null);
  return (
    <ReactionPicker
      value={reaction}
      onValueChange={setReaction}
      count={12 + (reaction ? 1 : 0)}
    >
      <View style={{ padding: 12, borderRadius: 14 }}>
        <Text>Shipped the new onboarding</Text>
      </View>
    </ReactionPicker>
  );
}
```

## Examples

### Your own reaction set

```tsx
<ReactionPicker
  reactions={[
    { key: "clap", emoji: "👏", label: "Applause" },
    { key: "fire", emoji: "🔥", label: "Fire" },
    { key: "party", emoji: "🎉", label: "Celebrate" },
  ]}
  value={reaction}
  onValueChange={setReaction}
>
  {card}
</ReactionPicker>
```

### On a chat bubble

```tsx
<ChatBubble side="sent" reaction={m.reaction} onReactionChange={(k) => react(m.id, k)}>{m.text}</ChatBubble>
```

## API reference

### ReactionPicker

`import { ReactionPicker } from "@/components/goodui-native/reaction-picker/reaction-picker";`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` (required) | `ReactNode` | – | The message or post that gets the reaction. Keep it non-interactive and put its tap in `onPress`. |
| `reactions` | `Reaction[]` | – | Reactions in the pill, five to seven work best. Defaults to love, like, haha, wow, sad, thanks. |
| `value` | `string \| null` | – | Key of the chosen reaction, `null` for none (controlled). |
| `defaultValue` | `string \| null` | `null` | Initial reaction when uncontrolled. |
| `onValueChange` | `(key: string \| null) => void` | – | Called with the new key, or `null` when the person picks their current reaction again to remove it. |
| `count` | `number` | – | Everyone's reactions on this item. Shown in the badge when above 1. |
| `badgePlacement` | `"leading" \| "trailing"` | `"trailing"` | Badge corner under the element. `trailing` suits received messages and posts, `leading` sent messages. |
| `showBadge` | `boolean` | `true` | Show the chosen reaction as a badge on the element's bottom edge. Default `true`. |
| `onPress` | `() => void` | – | A normal tap on the element. |
| `onOpenChange` | `(open: boolean) => void` | – | Called when the pill opens or closes. |
| `delayLongPress` | `number` | `300` | Hold duration in milliseconds before the pill opens. Default 300. |
| `previewStyle` | `StyleProp<ViewStyle>` | – | Shape of the lifted copy. Match the element's own radius; default `{ borderRadius: 20 }`. |
| `haptic` | `boolean` | `true` | Haptics on open, while sliding across reactions, and on the pick. Default `true`. |
| `disabled` | `boolean` | `false` | Dims the control and ignores presses. |
| `accessibilityLabel` | `string` | – | Screen reader label for content without text, such as a photo. Text content names itself (required on the web, where a label must contain the visible text). |
| `labels` | `Partial<ReactionPickerLabels>` | – | Overrides for the built-in strings (default reaction names and screen reader text); a GooduiLocaleProvider pack sets them app-wide. |
| `style` | `StyleProp<ViewStyle>` | – | Style for the outer container. |
| `testID` | `string` | – | Identifier for end-to-end tests. |

## Accessibility

- The element exposes one action per reaction ('React with Love', or 'Remove Love reaction' for the current one) plus 'Show reactions', so screen reader users react without the gesture.
- Its label gains 'you reacted with Love, 12 reactions' when there is a reaction (the badge itself is hidden from screen readers); picking and removing are announced.
- On the web a label must contain the visible text, so give accessibilityLabel only when the content has no text (a photo). Text content names the element there, and 'you reacted with Love' follows as visually hidden text.
- The pill is a menu of menu items, each 44 by 44 pt and labelled, with the current reaction marked selected.
- On the web the element is a focusable button with a popup: right-click, the context-menu key or Shift+F10 open the pill (Enter and Space too when there is no onPress), focus moves to the first emoji when opened from the keyboard, and Escape closes it. Android's back button closes it.
- The badge is decorative; its content is in the element's label.

## Motion

- While held the element squeezes to 0.96, then a copy lifts slightly above a dimmed backdrop.
- The pill grows out of the element's side on the bouncy spring and each emoji springs in 28 ms after the one before it.
- Sliding: magnification is computed on the UI thread from the finger's distance to each emoji (up to 1.6 times and 16 pt up), easing in as the finger enters the pill's band; the hovered emoji shows its name above it.
- On release the chosen emoji flies along an arc to the badge in 460 ms, shrinking to badge size; the badge, hidden until then, pops with the bouncy spring. The space for the badge opens under the element on the snappy spring.
- Reduced motion: no squeeze, lift, stagger or magnification (the hovered emoji grows a little to show focus); the pill fades in, the emoji does not fly and the badge fades in.

## Haptics

- tap when the pill opens
- select for each emoji the finger slides onto, and on the pick (haptic={false} turns them off)

## Performance

- Hit-testing and magnification run in worklets against the measured pill; React re-renders on open, on the pick and when the flight lands.
- The overlay renders through Portal only while open.

## Notes for AI

- value is a reaction key or null; onValueChange receives null when the person picks their current reaction again.
- count is the total shown in the badge (including the person's own); it only shows above 1.
- Keep children non-interactive and put a normal tap in onPress; the long press belongs to the picker.
- Match previewStyle's borderRadius to the element so the lifted copy has the same shape.
- badgePlacement 'leading' for sent messages, 'trailing' for received ones and posts. The badge overlaps the element's bottom edge and the picker adds room below for it.
- For chat use ChatBubble's reaction props rather than wrapping it yourself.

## Related

- [Chat bubble](https://goodui.dev/native/components/chat-bubble.md): Message bubbles that group into runs with one tail, carry a receipt that ticks forward (clock, a check that draws itself, two checks, both brightening when read) and spring up out of the composer; plus a TypingIndicator whose dots wave and a Composer whose mic morphs into a filled send arrow as soon as there is text.
- [Context menu](https://goodui.dev/native/components/context-menu.md): Long-press an element and it lifts out of the page above a dimmed backdrop while a menu springs out beside it; keep the finger down, slide onto an item and release to choose. Right-click opens it on the web.
- [Like button](https://goodui.dev/native/components/like-button.md): A like toggle with one moment of delight: the heart squashes and springs back filled, a ring expands and particles burst while the count rolls; it updates optimistically and rolls back if the request fails.

## Guidance for AI tools

Reaction picker: Long-press a message and a pill of emoji springs out of it one by one; slide the finger across and the emoji under it swells dock-style while its neighbours lean in; let go to pick and the emoji flies in an arc onto the message, landing in a badge with a pop. Use only the documented props. Keep accessibility, reduced motion and both color schemes intact when adapting it.

Full library index: https://goodui.dev/native/llms.txt
