react-native-animations

Encodes motion design and animation engineering philosophy for React Native apps using Reanimated, Gesture Handler, and Skia. Use when building or reviewing mobile animations, gestures, transitions, or interactive UI polish. Triggers on animation tasks, micro-interactions, gesture handling, or when the user mentions Reanimated, worklets, shared values, Skia, or mobile motion.

7 stars

Best use case

react-native-animations is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Encodes motion design and animation engineering philosophy for React Native apps using Reanimated, Gesture Handler, and Skia. Use when building or reviewing mobile animations, gestures, transitions, or interactive UI polish. Triggers on animation tasks, micro-interactions, gesture handling, or when the user mentions Reanimated, worklets, shared values, Skia, or mobile motion.

Teams using react-native-animations should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/react-native-animations/SKILL.md --create-dirs "https://raw.githubusercontent.com/madebyecho/agent-skills/main/skills/react-native-animations/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/react-native-animations/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How react-native-animations Compares

Feature / Agentreact-native-animationsStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Encodes motion design and animation engineering philosophy for React Native apps using Reanimated, Gesture Handler, and Skia. Use when building or reviewing mobile animations, gestures, transitions, or interactive UI polish. Triggers on animation tasks, micro-interactions, gesture handling, or when the user mentions Reanimated, worklets, shared values, Skia, or mobile motion.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

Related Guides

SKILL.md Source

# React Native Animations

A philosophy and decision framework for building great motion on React Native. Covers Reanimated (values, worklets, layout animations), Gesture Handler (pan, pinch, composition), and Skia (canvas-driven animation) — the three libraries that make mobile motion feel native in 2026.

This skill is opinionated. It does not cover the legacy `Animated` API, Moti, or Lottie — when those are the right tool, this skill does not apply.

## When to Apply

Reference these guidelines when:
- Building any animation or transition in a React Native or Expo app
- Adding gesture-driven interactions (swipe, drag, pinch, pull-to-refresh)
- Reviewing animation code for performance or polish
- Choosing between Reanimated, Skia, or native views for a motion task
- Deciding easing curves, durations, or spring parameters
- Handling Reduce Motion accessibility settings

## Foundational Philosophy

Three principles underlie every decision in this skill. When in doubt, return to these.

**1. Motion serves the spatial model.** On mobile, animation is not decoration — it is how users understand where content comes from and where it goes. Modals rise from the button that opened them. Drawers slide from the edge they're anchored to. A swipe that dismisses a card moves with the finger until the gesture ends. If motion does not teach the user something about the interface's structure, it should not exist.

**2. The UI thread is sacred.** Every animation that touches the JS thread is a visible stutter waiting to happen. Reanimated worklets, shared values, and native gesture handlers exist for exactly one reason: to run motion at 60–120fps on a thread that never blocks. Any code pattern that forces an animation back to JS is a bug.

**3. Motion should match device physics.** iOS users expect spring-based, slightly overshooting motion with deceleration-dominant curves. Android/Material users expect the "standard curve" — quick acceleration, slow deceleration. Don't ship the same linear tween on both platforms and call it cross-platform.

## Animation Decision Framework

Four questions, in order. Skip any of them and motion becomes noise.

### 1. Should this animate?

| Frequency of interaction | Recommendation |
|--------------------------|----------------|
| 100+ times/day | No animation. Users want instant. |
| Dozens/day | Reduce or remove. Consider scale-only feedback. |
| Occasional | Standard animation. |
| Rare (onboarding, celebration) | Add delight. |

Mobile-specific addition: **Does it serve orientation in the navigation stack?** A push transition teaches "this came from there." A fade teaches nothing spatial. If the answer is "it serves orientation," animate; otherwise default to instant.

### 2. What's the purpose?

Valid reasons to animate on mobile:
- **Spatial continuity** between screens or states
- **Gesture feedback** — the finger is dragging, something should move with it
- **State indication** — loading, success, error, selection
- **Preventing jarring layout shifts** — items appearing in a list, keyboard opening
- **Teaching gesture availability** — bottom sheet preview bounce, swipe affordance

If you cannot name one of these reasons, cut the animation.

### 3. Which easing?

Defaults that work everywhere:

| Use case | Easing | Rationale |
|----------|--------|-----------|
| Entering (mount, appear) | `Easing.out(Easing.cubic)` | Decelerates into place — iOS-feeling |
| Exiting (unmount, dismiss) | `Easing.in(Easing.cubic)` | Accelerates away |
| Moving / morphing | `Easing.inOut(Easing.cubic)` | Material "standard curve" |
| Gesture follow | `withSpring({ damping: 15, stiffness: 150 })` | Physics, not time |
| Infinite loader | `Easing.linear` | The only time linear is acceptable |

Never use `Easing.in` or `Easing.inOut` for mount animations — delayed initial movement feels sluggish.

For custom curves, prefer `cubicBezier(0.23, 1, 0.32, 1)` as an iOS-style entering curve. Material's standard curve is `cubicBezier(0.4, 0, 0.2, 1)`.

See `references/easing-and-timing.md` for the full reference table.

### 4. Which duration?

| Interaction | Duration |
|-------------|----------|
| Button press / scale feedback | 100–160ms |
| Toast, icon toggle, small UI | 150–200ms |
| Navigation push/pop, modal | 250–400ms |
| Drawer, bottom sheet | 300–500ms |
| Onboarding illustration | 500–1000ms |

**Never exceed 500ms for standard navigation.** Beyond that, the user perceives lag, not motion. Spring animations don't have a duration — they have physics, and physics should feel like the device.

## The Reanimated Mental Model

Before writing any animation code, internalize these concepts.

**`useSharedValue`** — a value that lives on the UI thread. Mutations don't trigger React re-renders. It is the atom of Reanimated.

```tsx
const offset = useSharedValue(0);
offset.value = 100; // No re-render. The UI thread sees the new value immediately.
```

**`useAnimatedStyle`** — a worklet that reads shared values and returns a style object. Runs on the UI thread whenever its shared values change.

```tsx
const animatedStyle = useAnimatedStyle(() => ({
  transform: [{ translateX: offset.value }],
}));
```

**`withTiming` / `withSpring`** — animation drivers that return values over time. Assign the return value to a shared value to animate it.

```tsx
offset.value = withSpring(100, { damping: 15, stiffness: 150 });
```

**Worklets** — functions marked with `'worklet'` that run on the UI thread. Gesture callbacks are automatically workletized. To call a JS function from a worklet, wrap it in `runOnJS`.

```tsx
const tap = Gesture.Tap().onEnd(() => {
  'worklet';
  runOnJS(logAnalytics)('tap');
});
```

**Layout animations** — declarative mount/unmount and reorder animations. The easiest path to polish.

```tsx
<Animated.View entering={FadeIn.duration(300)} exiting={FadeOut} />
```

**`Animated.View` is not `View`.** Regular `View` doesn't participate in the worklet render pipeline. Every component that consumes an animated style must be an `Animated.*` component.

**Critical bug to avoid:** mutating a shared value inside `useAnimatedStyle` causes undefined behavior — potentially an infinite loop. Shared values are written from event handlers, effects, or gesture callbacks — never from inside the style worklet.

See `references/reanimated-patterns.md` for API recipes.

## Gesture-Driven Animation

Gesture Handler v2 is a declarative API composed with Reanimated. This is where mobile motion becomes mobile.

**Core gestures:** `Gesture.Pan()`, `Gesture.Pinch()`, `Gesture.Tap()`, `Gesture.LongPress()`, `Gesture.Rotation()`, `Gesture.Fling()`.

**Composition:**
- `Gesture.Simultaneous(a, b)` — both active at once (pinch + pan in a photo viewer)
- `Gesture.Race(a, b)` — first to activate wins
- `Gesture.Exclusive(a, b)` — one blocks the others

**Canonical pan-to-dismiss pattern:**

```tsx
const translateY = useSharedValue(0);

const pan = Gesture.Pan()
  .onUpdate((e) => {
    translateY.value = Math.max(0, e.translationY);
  })
  .onEnd((e) => {
    const shouldDismiss = translateY.value > 100 || e.velocityY > 500;
    if (shouldDismiss) {
      translateY.value = withTiming(SCREEN_HEIGHT, { duration: 250 });
      runOnJS(onDismiss)();
    } else {
      translateY.value = withSpring(0, { damping: 15, stiffness: 150 });
    }
  });
```

Three things to notice:
1. **Velocity matters.** A fast flick should dismiss even before the threshold distance. Always read `event.velocityX` / `velocityY` in `onEnd`.
2. **Springs for snap-back.** Rubber-band-feeling returns use `withSpring`, not `withTiming`.
3. **`runOnJS` for commit.** Navigation, haptics, and analytics all cross the thread boundary via `runOnJS`.

**Haptic coordination:** fire `Haptics.impactAsync` via `runOnJS` at gesture commit points (activation, snap, dismiss). Never inside `onUpdate` — you'll haptic every frame.

**Interruption:** spring animations must be interruptible. If a new pan starts mid-snap-back, the shared value is simply reassigned and `withSpring` takes over from the current velocity. Never queue animations with callbacks when you could use `withSequence`.

See `references/gesture-handler-patterns.md` for composition recipes and the full pinch+pan+rotate photo viewer example.

## Layout Animations & List Transitions

The declarative path — use these first, before reaching for custom worklets.

**Mount/unmount animations:**
```tsx
<Animated.View entering={FadeIn.duration(300)} exiting={FadeOut.duration(200)} />
<Animated.View entering={SlideInRight.springify().damping(15)} exiting={SlideOutLeft} />
```

**List reordering:**
```tsx
<Animated.FlatList
  data={data}
  renderItem={renderItem}
  itemLayoutAnimation={LinearTransition.springify()}
/>
```

**Modifiers:** `.duration(ms)`, `.easing(Easing.out(Easing.cubic))`, `.springify()`, `.damping(15)`, `.delay(100)`, `.withCallback(cb)`, `.reduceMotion(ReduceMotion.System)`.

**Shared element transitions** (Reanimated 3+):
```tsx
<Animated.Image sharedTransitionTag="profile-photo" />
```

Shared tags on a source and destination screen automatically animate geometry between them. Requires the navigator to use the native stack.

## Skia for Advanced Motion

Reach for `@shopify/react-native-skia` when RN views can't do the job:

- Charts, graphs, paths, Bézier morphing
- Shaders, blur, color filters, blend modes
- Particle systems, game loops
- Custom gradients and masks
- Animated GIFs with frame control (`useAnimatedImageValue`)

**Interop is seamless in modern Skia + Reanimated.** Skia components accept shared values and derived values directly — no separate `useValue` hook needed:

```tsx
import { Canvas, Circle } from "@shopify/react-native-skia";
import { useDerivedValue, useSharedValue, withRepeat, withTiming } from "react-native-reanimated";

const r = useSharedValue(0);
useEffect(() => {
  r.value = withRepeat(withTiming(85, { duration: 1000 }), -1);
}, []);

return (
  <Canvas style={{ flex: 1 }}>
    <Circle cx={128} cy={128} r={r} color="cyan" />
  </Canvas>
);
```

**Time-based animation:** `useClock()` returns a shared value of elapsed milliseconds. Pipe it through `useDerivedValue` for trigonometric or procedural motion.

**Path interpolation:** `usePathInterpolation(progress, [0, 0.5, 1], [pathA, pathB, pathC])` morphs SVG paths as a shared-value progress moves from 0 to 1.

**When not to use Skia:** box-model UI (cards, lists, buttons). Skia runs in its own renderer — you lose accessibility tree integration, native text rendering, and standard touch handling. Use it for the 5% of your app where Reanimated-on-views isn't enough.

See `references/skia-animation-patterns.md` for canvas recipes.

## Performance Rules

Non-negotiables. Violating any of these means a dropped frame somewhere.

1. **Drive animations through shared values, not state.** Never `setState` in an animation loop.
2. **Prefer `transform` and `opacity`.** Reanimated *can* animate `width`, `height`, and `top` off-thread (unlike the legacy Animated API), but transforms are still cheaper and never trigger layout reflow of siblings.
3. **Never mutate a shared value inside `useAnimatedStyle`.** Infinite loop / undefined behavior.
4. **Don't `setState` in `onScroll` or `onGestureEvent`.** Use `useAnimatedScrollHandler` and gesture worklets. When you need JS state, use `useAnimatedReaction` with `runOnJS`.
5. **Gate hover/press state by platform.** `hover` doesn't exist on touch. Use `Pressable` states, gesture handlers, or `scale` feedback.
6. **Fire haptics at commit points, not per frame.** Wrap `Haptics.impactAsync` in `runOnJS` and call it in `onEnd` or threshold-crossing branches.
7. **Profile on mid-tier Android, not the iOS simulator.** The simulator lies. A real Pixel 6a or Samsung A-series tells the truth.
8. **Watch the UI thread frame rate, not just JS.** Perf Monitor shows both. If the UI thread is dropping frames, Reanimated isn't saving you — there's layout or paint work hiding somewhere.

See `assets/performance-checklist.md` for the pre-ship verification list.

## Accessibility: Reduce Motion

This is not a footnote. Users with vestibular disorders enable Reduce Motion to avoid nausea — animations that ignore this setting are a physical accessibility failure.

**`useReducedMotion()`** (from `react-native-reanimated`) — synchronous boolean. Preferred over `AccessibilityInfo.isReduceMotionEnabled()` because it works in worklets and doesn't require async.

**`ReducedMotionConfig`** — app-wide component. Place it at the root of the app to globally disable animations when the OS setting is on.

```tsx
import { ReducedMotionConfig, ReduceMotion } from 'react-native-reanimated';

<ReducedMotionConfig mode={ReduceMotion.System} />
```

**`.reduceMotion()` modifier** — per-animation override. Available on all `withTiming`, `withSpring`, and layout animation builders.

```tsx
<Animated.View
  entering={SlideInRight.duration(300).reduceMotion(ReduceMotion.System)}
/>
```

**The decision rule when Reduce Motion is on:**
- **Keep** opacity, color, and fade animations — they convey state without vestibular impact.
- **Remove** translate, scale, rotate, and parallax — collapse to instant or fade.

Example: a slide-up modal becomes a fade-in when Reduce Motion is on. The user still sees the modal appear; they just don't see it travel.

See `references/accessibility.md` for the full matrix. For audit-mode detection of missing reduce-motion checks, cross-reference the `react-native-accessibility` skill's `p2-reduce-motion-ignored` rule.

## Component Patterns

The mobile equivalent of Emil Kowalski's "button responsiveness" section. Lift these directly.

**Pressable scale feedback** (the 160ms "alive" feel):
```tsx
const scale = useSharedValue(1);
const animatedStyle = useAnimatedStyle(() => ({
  transform: [{ scale: scale.value }],
}));

<Pressable
  onPressIn={() => { scale.value = withSpring(0.97, { damping: 20, stiffness: 400 }); }}
  onPressOut={() => { scale.value = withSpring(1, { damping: 20, stiffness: 400 }); }}
>
  <Animated.View style={[styles.button, animatedStyle]} />
</Pressable>
```

**Toast / snackbar:** `entering={SlideInDown.springify().damping(18)}` + `exiting={SlideOutDown.duration(200)}`. Never exceed 300ms on either side.

**Bottom sheet snap:** pan gesture + `withSpring` with velocity: `translateY.value = withSpring(snapPoint, { velocity: event.velocityY, damping: 20, stiffness: 150 })`. Snap points at 0, 0.5, 1 of screen height.

**Pull-to-refresh:** `useAnimatedScrollHandler` reads `event.contentOffset.y`, drives a `useSharedValue` for rubber-band overscroll, fires a refresh via `runOnJS` at threshold.

**Skeleton shimmer:** `withRepeat(withTiming(1, { duration: 1200 }), -1)` drives a `translateX` on a gradient overlay. Skia gives the nicest gradient; Reanimated-on-view works for simpler skeletons.

**Hero / shared element:** `sharedTransitionTag="id"` on source and destination screens. Requires a native stack navigator.

## Anti-Patterns

Short, punchy, often-seen, always wrong.

- **Using the legacy `Animated` API for interactive elements.** It's JS-thread-driven. Use Reanimated.
- **`setState` inside `onScroll` or `onGestureEvent`.** Use worklets + `useAnimatedReaction`.
- **Animating `marginTop` or `top` instead of `translateY`.** Triggers layout reflow.
- **Forgetting the `'worklet'` directive on callbacks passed to gestures.** They run on JS and stutter.
- **Chaining animations with `withTiming(...).then(...)`-style callbacks** when `withSequence` exists.
- **Mutating shared values inside `useAnimatedStyle`.** Undefined behavior.
- **Ignoring `useReducedMotion`.** Physical accessibility failure.
- **Using `LayoutAnimation` from core React Native.** It's JS-thread-driven and uneven across platforms. Use Reanimated layout animations instead.
- **Reaching for Skia for a button press.** Skia is for canvas work, not UI kit polish.
- **Driving a Reanimated animation from a React `useEffect` that depends on a changing prop** without reading from a shared value. You're re-creating the animation every render.

## Review Format

When reviewing existing animation code, output a **markdown table with Before | After | Why columns.** Never use separate "Before:" and "After:" lines — the table makes the tradeoff visible at a glance.

Example:

| Before | After | Why |
|--------|-------|-----|
| `top: withTiming(100)` | `transform: [{ translateY: withTiming(100) }]` | `top` triggers layout; `translateY` is compositor-only |
| `setState({ x })` in `onUpdate` | `sharedValue.value = e.translationX` | `setState` crosses to JS thread every frame |
| `Animated.timing(...)` (legacy) | `withTiming(...)` (Reanimated) | Legacy API runs on JS thread |

## External Resources

- [Reanimated docs](https://docs.swmansion.com/react-native-reanimated) — canonical API reference
- [Gesture Handler docs](https://docs.swmansion.com/react-native-gesture-handler) — gesture composition and v2 API
- [React Native Skia docs](https://shopify.github.io/react-native-skia) — canvas, shaders, animated images
- [Apple HIG: Motion](https://developer.apple.com/design/human-interface-guidelines/motion) — iOS motion principles
- [Material Design Motion](https://m3.material.io/styles/motion/overview) — Android motion specs and tokens
- [Emil Kowalski's animations.dev](https://animations.dev) — web motion theory that translates to mobile
- [React Native Reanimated Accessibility](https://docs.swmansion.com/react-native-reanimated/docs/guides/accessibility/) — Reduce Motion integration

## Supporting Files

Loaded on-demand during execution:

- `references/reanimated-patterns.md` — Full Reanimated API recipes (shared values, hooks, animation drivers, interpolation)
- `references/gesture-handler-patterns.md` — Gesture composition recipes including pan+pinch+rotate viewer
- `references/skia-animation-patterns.md` — Skia canvas animation patterns with Reanimated interop
- `references/easing-and-timing.md` — Consolidated easing curves, spring presets, duration tokens
- `references/accessibility.md` — Reduce Motion integration patterns and decision matrix
- `assets/decision-flowchart.md` — Quick-reference decision flowchart
- `assets/performance-checklist.md` — Pre-ship performance verification checklist

Related Skills

react-native-skia-shaders

7
from madebyecho/agent-skills

Comprehensive SKSL (Skia Shading Language) shader techniques for react-native-skia — ray marching, SDF modeling, procedural noise, lighting, post-processing, image filters, BackdropFilter, child shaders, gesture- and Reanimated-driven uniforms. Triggers on tasks involving Shader, RuntimeEffect, SKSL, custom visual effects, animated backgrounds, image distortion, blur, glow, glass / liquid effects, or any request to "write a shader" in a React Native or Expo app using @shopify/react-native-skia.

react-native-accessibility

7
from madebyecho/agent-skills

Automatically applies accessibility best practices to React Native and Expo projects. Use when working on mobile apps that need VoiceOver (iOS) or TalkBack (Android) support, WCAG compliance, or accessibility audits. Triggers on React Native accessibility tasks, a11y improvements, or when the user mentions accessibility, VoiceOver, TalkBack, or screen reader support.

swift-accessibility

7
from madebyecho/agent-skills

Automatically applies accessibility best practices to Swift projects (SwiftUI and UIKit). Use when working on iOS/macOS projects that need VoiceOver support, Dynamic Type, WCAG compliance, or accessibility audits. Triggers on Swift accessibility tasks, a11y improvements, or when the user mentions accessibility, VoiceOver, or Dynamic Type.

skill-creator

7
from madebyecho/agent-skills

Create new skills, modify and improve existing skills, and measure skill performance. Use when users want to create a skill from scratch, update or optimize an existing skill, run evals to test a skill, benchmark skill performance with variance analysis, or optimize a skill's description for better triggering accuracy.

md-to-pdf

7
from madebyecho/agent-skills

Converts markdown files into professionally styled PDF documents. Use this skill whenever the user asks to generate a PDF, export markdown as PDF, convert .md to .pdf, or create a printable version of a document. Also triggers when the user wants status-colored tables, styled documentation export, or says things like "make this a PDF", "I need a PDF version", "export this doc". Supports auto orientation, optional keyword-based cell coloring, and graceful engine fallback.

native-data-fetching

31392
from sickn33/antigravity-awesome-skills

Use when implementing or debugging ANY network request, API call, or data fetching. Covers fetch API, React Query, SWR, error handling, caching, offline support, and Expo Router data loaders (useLoaderData).

API IntegrationClaude

fp-ts-react

31392
from sickn33/antigravity-awesome-skills

Practical patterns for using fp-ts with React - hooks, state, forms, data fetching. Use when building React apps with functional programming patterns. Works with React 18/19, Next.js 14/15.

fp-react

31392
from sickn33/antigravity-awesome-skills

Practical patterns for using fp-ts with React - hooks, state, forms, data fetching. Works with React 18/19, Next.js 14/15.

competitor-alternatives

31392
from sickn33/antigravity-awesome-skills

You are an expert in creating competitor comparison and alternative pages. Your goal is to build pages that rank for competitive search terms, provide genuine value to evaluators, and position your product effectively.

Marketing Content GenerationClaude

building-native-ui

31392
from sickn33/antigravity-awesome-skills

Complete guide for building beautiful apps with Expo Router. Covers fundamentals, styling, components, navigation, animations, patterns, and native tabs.

react

389
from udecode/better-convex

This AI agent skill guides the generation of modern React components, incorporating best practices such as destructured props, leveraging compiler optimizations, and proper use of React Effects. It also ensures compatibility and utilizes Tailwind CSS v4 syntax.

Coding & DevelopmentClaude

react

30
from purrgrammer/grimoire

This skill provides comprehensive knowledge of React 19, including hooks, server components, concurrent features, and React DOM APIs. It guides AI agents on modern React architecture, patterns, and best practices for building robust applications.

Coding & DevelopmentClaude