mcpField
R
reactiive

reactiive

github.com/enzomanuelmanganoDesign & UI1041 installsUpdated 31w ago
Repository

About reactiive

reactiive is a skill for React Native developers building animations, covering Reanimated shared values, gestures, springs, and layout animations. It also offers animation design principles and guidance on common pitfalls.

Key features

  1. 01Use Reanimated shared values and animated styles
  2. 02Organize animation state with derived values
  3. 03Run callbacks when shared values change
  4. 04Design gesture and spring animations
  5. 05Build layout and shared-element transitions
  6. 06Troubleshoot issues such as transform order

Use cases

  1. 01Add feedback animations to touch interactions
  2. 02Build drag and swipe interactions
  3. 03Create element entry, exit, and layout animations
  4. 04Design shared-element transitions between screens

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/enzomanuelmangano/skill --skill reactiive

After installing, type/reactiivein Claude Code to confirm it loaded.

Bundled files

References and scripts installed along with SKILL.md.

design-engineering.md
easings.md
gestures.md
scroll-animations.md
sequences.md
skia-patterns.md
springs.md
text-animations.md
worklets.md

Details

From the project README

React Native Animation Craft

Initial Response

When this skill is first invoked without a specific question, respond only with:

I'm ready to help you build animations that feel right.

Do not provide any other information until the user asks a question.


The Recipe

Every animation needs three ingredients. Miss any and things will crash.

// 1. A shared value that lives on the UI thread
const scale = useSharedValue(1);

// 2. An animated style that reads from it
const rStyle = useAnimatedStyle(() => ({
  transform: [{ scale: scale.get() }],
}));

// 3. An Animated.View that renders it
<Animated.View style={[styles.square, rStyle]} />

The shared value holds the data. The animated style transforms it into properties. The Animated.View renders it.

Once you get this, everything else is just variations.

Note: Use .get() and .set() instead of .value for React Compiler compatibility.


The Four Hooks

HookPurpose
useSharedValueCreate a value on the UI thread
useAnimatedStyleTurn shared values into styles
useDerivedValueCompute new values from existing ones
useAnimatedReactionTrigger side effects when values change

useDerivedValue is my favorite — it's going to be extremely important.


useDerivedValue

Define a new shared value that automatically recomputes based on other shared values.

const isDragging = useSharedValue(false);

const rotate = useDerivedValue(() => {
  return withSpring(isDragging.get() ? '45deg' : '0deg');
});

const scale = useDerivedValue(() => {
  return withSpring(isDragging.get() ? 0.9 : 1);
});

The magic: wrap the return value with animation functions. When isDragging changes, the derived values animate smoothly.

Chaining Derived Values
const color = useDerivedValue(() => {
  if (isDragging.get()) return '#0099ff';
  if (translateY.get() < 0) return 'black';
  if (translateY.get() > 0) return 'white';
  return '#0099ff';
});

const animatedColor = useDerivedValue(() => {
  return withTiming(color.get());
});

Chain them. The color derives from state. The animatedColor adds animation. Clean and declarative.


useAnimatedReaction

Like useEffect for the UI thread. It watches a shared value and fires a callback when it changes.

useAnimatedReaction(
  () => left.get(),
  (curr, prev) => {
    if (curr !== prev && curr !== 0) {
      cancelAnimation(scale);
      scale.set(0);
      scale.set(withSpring(1, { mass: 0.5 }));
    }
  },
);

The check for curr !== 0 prevents firing on mount. Cancel any running animation before starting a new one.


Common Mistakes

Transform Order

The order of transforms matters. Scale before translate multiplies your translation.

// ✗ Wrong: translateX of 50 becomes 50 * 1.5 = 75
transform: [
  { scale: 1.5 },
  { translateX: 50 },
]

// ✓ Correct: translate first, then scale
transform: [
  { translateX: translateX.get() },
  { translateY: translateY.get() },
  { scale: scale.get() },
  { rotate: rotate.get() },
]
Array Index Keys

Using array indices as keys breaks layout animations.

// ✗ Wrong: indices shift when items change
{items.map((item, index) => (
  <Animated.View key={index} layout={LinearTransition} />
))}

// ✓ Correct: stable unique IDs
{items.map((item) => (
  <Animated.View key={item.id} layout={LinearTransition} />
))}
iOS Bounce

Scroll views bounce past their bounds on iOS. Without clamping, your interpolations break.

import { clamp } from 'react-native-reanimated';

// Raw progress can go negative or exceed 1
const rawProgress = offset / maxOffset;

// Clamped progress stays valid
progress.set(clamp(rawProgress, 0, 1));
Timing for Transforms

Timing animations on transforms feel robotic. Use springs.

// ✗ Mechanical
scale.set(withTiming(1.2, { duration: 300 }));

// ✓ Natural
scale.set(withSpring(1.2));
Missing Velocity

When ending gestures, pass the velocity. Without it, the animation feels disconnected.

.onFinalize((event) => {
  translateX.set(withSpring(0, {
    velocity: event.velocityX,
  }));
});

Springs

Springs feel natural because they simulate physics. Real objects don't move with fixed durations.

The dampingRatio

My range: 0.95 - 1. Stay here for professional UI.

dampingRatioBehavior
0.95 - 1Critically damped — no overshoot
< 0.95Underdamped — intentionally bouncy
> 1Overdamped — sluggish, avoid
// Default: clean, professional
scale.set(withSpring(1, { dampingRatio: 1 }));

// Slightly softer feel
translateY.set(withSpring(0, { dampingRatio: 0.95 }));
When to Use What
Animation TypeUse
Transforms (scale, translate, rotate)withSpring
Static properties (opacity, colors)withTiming + easing
// ✓ Transforms: springs
scale.set(withSpring(1.1));

// ✓ Opacity: timing
opacity.set(withTiming(1, { duration: 200 }));

The Context Pattern

Pan gestures jump back to start on new touches. The solution: save position at gesture begin.

const translateX = useSharedValue(0);
const context = useSharedValue({ x: 0 });

const panGesture = Gesture.Pan()
  .onBegin(() => {
    context.set({ x: translateX.get() });
  })
  .onUpdate((event) => {
    translateX.set(event.translationX + context.get().x);
  });

Touch Feedback with pressto

Every tap needs feedback. pressto provides animated touchables that feel right.

import { PressableScale } from 'pressto';

<PressableScale onPress={handlePress}>
  <View style={styles.button}>
    <Text>Tap me</Text>
  </View>
</PressableScale>
Global Haptics
import { PressablesConfig } from 'pressto';
import * as Haptics from 'expo-haptics';

<PressablesConfig
  globalHandlers={{
    onPress: () => Haptics.selectionAsync(),
  }}
>
  {children}
</PressablesConfig>

Layout Animations

The easiest way to animate mount/unmount.

import Animated, { FadeIn, FadeOut, LinearTransition } from 'react-native-reanimated';

{isVisible && (
  <Animated.View
    entering={FadeIn.duration(300)}
    exiting={FadeOut.duration(200)}
    layout={LinearTransition.springify()}
  />
)}

Exiting animations are hard to build manually. Reanimated handles the delay, animation, and unmount automatically.

LinearTransition.springify()
// Snappy
LinearTransition.springify().mass(0.3).damping(20).stiffness(250)

// Smooth
LinearTransition.springify().mass(1).damping(20).stiffness(120)

Shared Transitions

Navigate between screens while an element morphs between them.

import { Image } from 'expo-image';
import Animated from 'react-native-reanimated';

const AnimatedImage = Animated.createAnimatedComponent(Image);

// Home screen
<AnimatedImage
  source={{ uri: item.url }}
  sharedTransitionTag={`image-${item.id}`}
  style={{ width: 100, height: 100 }}
/>

// Detail screen - same tag
<AnimatedImage
  source={{ uri: item.url }}
  sharedTransitionTag={`image-${item.id}`}
  style={{ width: '100%', aspectRatio: 1 }}
/>

Use containedTransparentModal presentation for the detail screen.


The Magic Button

This is what a memorable animation looks like. Scale, rotating gradient, blur — all on tap.

const isTouched = useSharedValue(false);

const scale = useDerivedValue(() => {
  return withSpring(isTouched.get() ? 1.2 : 1);
});

const rotate = useDerivedValue(() => {
  return withTiming(isTouched.get() ? Math.PI * 2 : 0, { duration: 1000 });
});

<Canvas style={{ width: realWidth, height: realHeight }}>
  <Group origin={vec(centerX, centerY)} transform={[{ rotate: rotate.get() }]}>
    <RoundedRect x={realX} y={realY} width={width} height={height} r={width / 2}>
      <SweepGradient c={center} colors={['cyan', 'magenta', 'yellow', 'cyan']} />
      <BlurMask blur={20} style="solid" />
    </RoundedRect>
  </Group>
</Canvas>

Functional animations confirm touches. Memorable animations surprise. The magic button does both.


Functional vs Memorable

Functional animations are what the user expects. Tap a button, get feedback. They're essential — without them, the app feels broken. But they don't make users remember your app.

Memorable animations are what the user doesn't expect. A gradient that glows and rotates. A 3D flip. These animations surprise.

Build the functional first. Then add the memorable.


Philosophy

Every Animation Needs a Job
JobAnimation
Confirm touchScale down on press
Show connectionMorph card into detail
Direct attentionFade in from trigger direction
Maintain orientationShared element between screens
Indicate progressPulse or shimmer

If you can't name the job, delete the animation.

Match Intensity to Frequency

The more often something happens, the more subtle it should be.

// Happens constantly: clean, no bounce
const buttonScale = withSpring(pressed ? 0.97 : 1, {
  dampingRatio: 1,
});
Pick Constraints Early
  • Three spring configs for the whole app
  • Scale and opacity only for touch feedback
  • One easing curve for all timing animations

Decisions made once, applied everywhere. The app feels cohesive because it is.


Reference Files


Resources

Comments

Markdown supported