Shipnative
ShipnativeShipnative
Sign in
GuideAugust 2026 · 9 min read

React Native Animations: Reanimated Guide (2026)

Animation is the difference between an app that works and an app that feels made. It’s also the first place a React Native app betrays itself — a card that judders as it slides, a sheet that lags a finger behind the drag, a list that hitches every time data arrives. Almost all of it comes down to one thing: which thread the animation runs on. This guide covers the rule that fixes it, the Reanimated patterns worth memorising, and the handful of style properties you should never animate frame by frame.

The one rule: animations belong on the UI thread

A React Native app runs your code on the JavaScript thread and draws on the UI thread. If the animation is computed in JavaScript, then every frame depends on the JS thread being free at that exact moment. It usually is — right up until the user does something interesting, like scrolling a list while a response lands. Then the JS thread is busy, frames get skipped, and the animation stutters at precisely the moment the user was paying attention.

react-native-reanimated solves this by compiling small functions called workletsthat run on the UI thread. The animation keeps producing frames whether or not JavaScript is busy. That’s the entire pitch, and it’s enough of one that Reanimated is now the default in essentially every serious React Native codebase — including the navigation and gesture libraries you’re probably already using.

npx expo install react-native-reanimated

The core pattern: shared value → animated style

Reanimated has one idea you need to hold: a shared value is a number that lives outside React. Changing it does notre-render the component — it updates a style directly on the UI thread. Here’s a press-to-scale button, which is the smallest useful example:

import { Pressable, Text } from 'react-native';
import Animated, {
  useSharedValue, useAnimatedStyle, withSpring,
} from 'react-native-reanimated';

export default function ScaleButton({ onPress, label }) {
  const scale = useSharedValue(1);

  // this callback is a worklet — it runs on the UI thread
  const animatedStyle = useAnimatedStyle(() => ({
    transform: [{ scale: scale.value }],
  }));

  return (
    <Pressable
      onPressIn={() => { scale.value = withSpring(0.95, { damping: 15 }); }}
      onPressOut={() => { scale.value = withSpring(1, { damping: 15 }); }}
      onPress={onPress}
    >
      <Animated.View style={[styles.button, animatedStyle]}>
        <Text style={styles.label}>{label}</Text>
      </Animated.View>
    </Pressable>
  );
}

Three things are load-bearing there:

  • It’s Animated.View, not View. A plain View has nowhere to receive the UI-thread updates, and your animation silently does nothing.
  • You read and write scale.value, never scale itself.
  • withSpring is physics, not a duration. withTiming takes a duration and easing when you want an exact length — fades and colour changes usually do.

Animate these, not those

PropertyPer-frame costWhyVerdict
transform (translate, scale, rotate)CheapComposited — no layout passAnimate freely
opacityCheapCompositedAnimate freely
backgroundColor / borderColorModerateRepaint, no reflowFine in moderation
width / height / padding / marginExpensiveTriggers layout on every frameUse scale, or a layout animation
flex / position valuesExpensiveRe-lays out siblings tooAvoid per-frame

The practical version: if you find yourself animating height to expand a card, reach for scaleor a layout animation instead. A growing height re-lays out everything below it sixty times a second; a scale transform doesn’t touch layout at all.

Free wins: entering, exiting, and layout

Most of the motion in a polished app isn’t hand-built — it’s mount, unmount, and reflow. Those are one prop each:

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

{items.map((item, i) => (
  <Animated.View
    key={item.id}
    entering={FadeIn.delay(i * 40)}   // staggered arrival
    exiting={FadeOut}
    layout={LinearTransition}          // siblings slide as one is removed
  >
    <ItemRow item={item} />
  </Animated.View>
))}

layout is the underrated one. Delete a row from a list without it and everything below teleports upward; with it, the gap closes smoothly and the change reads as an event rather than a glitch.

Keep the stagger delay small. Forty milliseconds per item feels alive; two hundred feels like the app is making you wait, and past about eight items you should stagger only the first few and let the rest arrive together.

Gestures: when the finger drives

A swipe-to-dismiss card is the canonical case where JS-thread animation visibly fails — any lag between finger and card is instantly obvious. react-native-gesture-handlerreads the touch natively and writes straight into a shared value, so there’s no round trip:

import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import Animated, {
  useSharedValue, useAnimatedStyle, withSpring, runOnJS,
} from 'react-native-reanimated';

function SwipeableCard({ onDismiss, children }) {
  const x = useSharedValue(0);

  const pan = Gesture.Pan()
    .onUpdate((e) => { x.value = e.translationX; })
    .onEnd((e) => {
      if (Math.abs(e.translationX) > 120) {
        x.value = withSpring(Math.sign(e.translationX) * 500);
        runOnJS(onDismiss)();   // crossing back to the JS thread
      } else {
        x.value = withSpring(0);
      }
    });

  const style = useAnimatedStyle(() => ({
    transform: [{ translateX: x.value }],
    opacity: 1 - Math.min(Math.abs(x.value) / 300, 0.6),
  }));

  return (
    <GestureDetector gesture={pan}>
      <Animated.View style={style}>{children}</Animated.View>
    </GestureDetector>
  );
}

runOnJS is the piece people forget. Gesture callbacks are worklets on the UI thread — calling a normal React callback such as onDismiss directly from one throws. Wrap it, and only for things that genuinely need React: state updates, navigation, network calls. Everything visual should stay on the UI thread.

Judging motion honestly

  • Test in release mode on hardware.Dev builds run unoptimised JavaScript, and the iOS simulator draws on your Mac’s GPU. Both lie in opposite directions.
  • Test on the cheapest Android you own. That device is your real frame budget.
  • Respect reduce-motion. Read AccessibilityInfo.isReduceMotionEnabled() and shorten or drop non-essential motion. Some users get motion sick; App Store reviewers occasionally check.
  • Under 300ms for interface motion. Anything longer stops reading as feedback and starts reading as latency.

For everything around animation that affects perceived speed — list virtualisation, image decoding, re-render churn — the performance fixes guide covers the rest of the frame budget.

The shortcut: generate the motion, tune the feel

The Reanimated setup — installing it, wiring shared values, remembering Animated.View and runOnJS— is mechanical. What isn’t mechanical is deciding that this spring is a touch too bouncy, which you can only do by watching it on a phone.

Describe your app in ShipNative and it generates the screens with Reanimated already wired — press feedback, list entrances, screen transitions — running live on your device, so tuning is a prompt away rather than a rebuild away. Then export the whole Expo project and take the motion with you.

Build an animated app free

Describe your app in one sentence and watch it move on your own phone in minutes at shipnative.dev. No credit card, full React Native source export whenever you want it.

Frequently Asked Questions

Should I use Animated or Reanimated in React Native?

Reanimated, for anything a user can interrupt. The core Animated API runs its driver in JavaScript unless you set useNativeDriver, and even then it cannot animate layout properties like width or height natively. Reanimated runs the animation itself on the UI thread, so it keeps running smoothly even while JavaScript is busy rendering a list or parsing an API response.

Why does my React Native animation stutter?

Almost always because the animation is being computed on the JavaScript thread while that thread is doing something else — fetching, mapping a large array, or re-rendering a list. Move the animation into a Reanimated worklet so it runs on the UI thread, and check that you are animating transform and opacity rather than width, height, or margin.

What is a worklet in Reanimated?

A worklet is a JavaScript function that gets compiled to run on the UI thread instead of the JS thread. Functions passed to useAnimatedStyle, useDerivedValue, and gesture callbacks are worklets automatically. Because they run in a separate context, they can read shared values instantly but cannot call ordinary JS functions directly — you use runOnJS for that.

Do I need react-native-gesture-handler for animations?

Only for gesture-driven ones — a swipeable card, a draggable sheet, a pinch-to-zoom image. Gesture Handler processes touches natively and hands the values straight to Reanimated on the UI thread, so a drag tracks your finger with no lag. For simple entrance, press, and layout animations, Reanimated alone is enough.

Can an AI app builder add animations to my app?

Yes, for the common ones — screen transitions, list item entrances, press feedback, animated tab bars, and skeleton loaders. Describe the motion you want and ShipNative wires Reanimated with shared values and worklets, then previews it on your phone, which is the only place you can honestly judge whether a spring feels right.

→

10 React Native Performance Fixes

The rest of the frame budget — lists, images, re-renders, and startup time.

Read guide →
→

React Native Bottom Sheet

The most common gesture-plus-animation component, done properly.

Read guide →

Ship a real React Native app today

Describe, preview, and export Expo code — free to start.

Build with ShipNative →
ShipNative logoShipnative

Build mobile apps with AI. Describe, preview, and ship to iOS & Android in minutes.

Features

Text to App AIApp Generator from ScreenshotPRD to Mobile App

Tools

All free toolsApp Cost CalculatorApp Name GeneratorApp Store Keyword ToolReact Native Components

Blog

All blog postsHow to Build an App Without CodingBest AI Tools for Real Mobile AppsExpo EAS App Store ChecklistLovable, Cursor & v0 for MobileBest AI App Builders in 2026React Native AI App Builder Guide

Legal

FAQTerms of ServicePrivacy Policy

© 2026 ShipNative. All rights reserved.