Skip to content
Native Animation
Magnify Reaction Picker animation preview

This preview uses fictional demo content. See the props below for the customization options available in this component.

npx nativeanimation add magnify-reaction-picker
GlassSkia

Magnify Reaction Picker

A long-press reaction picker with a magnifying dock and configurable messages, reaction count, background color, and fonts.

$9 for this animation

Add to your project

Copy the component and its assets into your app with the Native Animation CLI, or paste the agent prompt into Claude Code, Codex or Cursor once the MCP server is connected.

About this animation

A long-press reaction picker with a magnifying dock and configurable messages, reaction count, background color, and fonts.

Why this one

Most reaction rows jump between fixed active states. This version derives every icon's scale, lift, label, and neighbor spacing from the finger's exact horizontal position, so the dock feels direct at any drag speed. Selection then becomes a legible event: the chosen reaction follows a curved path, settles into a persistent count chip, and releases a restrained eight-spark burst.

How it works

  1. A 350 ms long press activates a pan gesture over the message and opens the glass reaction capsule from the touch area.
  2. One continuous fingerX shared value drives a Gaussian influence curve for all six reactions. Nearby icons enlarge and rise while adjacent icons move aside. The reaction row sits outside the clipped glass background, and magnified discs keep at least 16 pt of clearance from the container edges.
  3. Releasing projects the current horizontal position to the nearest reaction and reports it on the JavaScript thread.
  4. The selected Lucide icon travels along a quadratic curve to the target message, then a spring reveals the persistent count chip.
  5. The component measures its own container, so the conversation, target, picker, and flight destination adapt without reading the window size.

Install

npx expo install expo-blur expo-image expo-linear-gradient @shopify/react-native-skia react-native-gesture-handler react-native-reanimated react-native-worklets

Usage

import MagnifyReactionPicker from './MagnifyReactionPicker';

export function AlbumChat() {
  return (
    <MagnifyReactionPicker
      title="Weekend album"
      messages={messages}
      targetMessageId="cover-photo"
      onReactionSelect={({ label }) => saveReaction(label)}
    />
  );
}

Use the ref to drive a showcase or select a reaction programmatically:

import { useRef } from 'react';
import MagnifyReactionPicker, {
  type MagnifyReactionPickerRef,
} from './MagnifyReactionPicker';

export function ReactionPreview() {
  const pickerRef = useRef<MagnifyReactionPickerRef>(null);

  return (
    <MagnifyReactionPicker
      ref={pickerRef}
      brandName="Camera"
      sentColorStart="#6D5BFF"
      sentColorEnd="#8B5CF6"
      reactionCount={3}
      messages={messages}
      targetMessageId="favorite"
    />
  );
}

The forwarded ref exposes open(), select(index), play(), and reset().

Props

PropTypeDefaultDescription
titlestring'Shared album'Conversation heading.
subtitlestring'4 members'Supporting member count.
brandNamestring'Camera'Product name shown beside the brand tile.
memberInitialsreadonly [string, string, string]['AM', 'JR', 'SK']Initials used by the overlapping member avatars.
messagesreadonly ReactionMessage[]DEFAULT_MESSAGESFive messages used to compose the thread.
targetMessageIdstring'target'ID of the message that receives the reaction picker.
timeLabelstring'9:41 AM'Time label placed between message groups.
initialReactionnumber-1Index selected when the component mounts; -1 shows no chip.
reactionCountnumber1Count shown in the selected reaction chip.
backgroundColorstring'#0B0B0F'Dark screen color.
sentColorStartstring'#6D5BFF'First color of sent-message bubbles.
sentColorEndstring'#8B5CF6'Second color of sent-message bubbles.
fontFamilystringundefinedTypeface for body copy.
mediumFontFamilystringundefinedTypeface for headings and labels.
monoFontFamilystringundefinedTypeface for time and reaction counts.
onReactionSelect(reaction: { index: number; label: string }) => voidundefinedCalled when a reaction settles on the message.
accessibilityLabelstring'Message reaction picker'Screen-reader label for the target message.
styleStyleProp<ViewStyle>undefinedOptional style for the outer container.
testIDstringundefinedTest identifier applied to the outer container.

Compatibility

  • Expo SDK 58
  • React Native 0.88
  • Reanimated 4.7 and Worklets 0.13
  • Skia 2.14
  • iOS: supported
  • Android: supported
  • Web: supported
  • Works in Expo Go because all native dependencies are included in Expo SDK 58

Accessibility

Every reaction is a labeled button with a 44 pt hit area, and the target exposes an accessibility action that opens the picker. Tapping any reaction is an alternative to dragging. With reduced motion enabled, magnification, message scaling, arc flight, spring bounce, and particles are removed; opening and selection use short fades instead.

Customization notes

The picker follows the gesture and springs; the optional sweep keeps its scheduled selection synchronized.