DEV Community

Cover image for Reanimated vs Moti vs Skia: Which React Native Animation Library Should You Actually Use?
Hugo Rus
Hugo Rus

Posted on Originally published at rapidnative.com

Reanimated vs Moti vs Skia: Which React Native Animation Library Should You Actually Use?

Every React Native team eventually asks the same question: Reanimated, Moti, or Skia?

The framing is wrong. These three don't compete, they compose. Here's the mental model that will save you a bad refactor.

  • Reanimated is the runtime. The other two depend on it.
  • Moti is a declarative wrapper over Reanimated. Less code, less control.
  • Skia is a rendering engine, not an animation library. It accepts Reanimated shared values directly.
  • Install Reanimated and Moti on day one. Add Skia when the design demands it.
  • Pick per interaction, not per app.

The one-sentence version of each

Reanimated. The animation runtime. Runs worklets on the UI thread through JSI. Everything else you install depends on it directly or indirectly.

Moti. A Framer Motion-style declarative wrapper over Reanimated. Less code for common transitions.

Skia. Google's 2D graphics engine, the one behind Chrome and Flutter, exposed as React Native components. Not an animation library. A rendering engine that happens to animate beautifully when you feed it shared values.

Reanimated in about ten lines

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

const x = useSharedValue(0);
const style = useAnimatedStyle(() => ({
  transform: [{ translateX: x.value }],
}));

// somewhere in a handler:
x.value = withSpring(200);

return <Animated.View style={[styles.box, style]} />;
Enter fullscreen mode Exit fullscreen mode

x is a shared value, and it lives on the UI thread. useAnimatedStyle is a worklet that reruns when x changes. A blocked JS thread doesn't stutter the animation, which is the entire point.

The same thing in Moti

<MotiView
  from={{ translateX: 0 }}
  animate={{ translateX: 200 }}
  transition={{ type: 'spring' }}
/>
Enter fullscreen mode Exit fullscreen mode

Same worklets underneath. Half the code.

That's the trade: you lose direct access to the shared value, which matters the moment you want to drive it from a gesture. For a mount transition you'll never miss it. For a drag you'll rewrite it.

Where each one wins

You need Use
A fade-in on mount Moti
A pan gesture moving a card Reanimated
A skeleton loader Moti
Pinch-to-zoom on an image Reanimated
An animated chart with 60+ points Skia + Reanimated
A shader-driven splash screen Skia
A stagger animation on a list Moti
A blur that follows a bottom sheet Skia + Reanimated

The composition detail people miss

Skia components accept Reanimated shared values as props directly. No createAnimatedComponent, no useAnimatedProps wrapper.

const progress = useSharedValue(0);

// progress updates, Skia canvas repaints, all on the UI thread
<Canvas style={{ flex: 1 }}>
  <Circle cx={progress} cy={100} r={40} color="hotpink" />
</Canvas>
Enter fullscreen mode Exit fullscreen mode

That single fact is what made Victory Native XL and every other modern chart library possible. Before it, animating a canvas meant round-tripping through JS every frame.

The performance gotcha

You will not hit a performance ceiling because you picked the "wrong" library. All three drive animations on the UI thread.

You will hit one if you animate a property that forces a layout pass every frame. Reanimated can push a shared value at 120fps, but if consuming it triggers layout, you're queued behind that layout and you drop frames regardless of which library issued the update.

Animate transform and opacity. Animating width, height, top, or left is the version of this mistake people actually ship, and all three libraries will let you do it.

The rule I use

Pick per interaction, not per app.

A screen with a draggable card and a hero image wants Reanimated for the drag and Moti for the hero. Separate primitives, separate concerns.

The moment you try to standardise on one library, you make one of those two interactions worse. Usually the drag, because that's the one where the abstraction costs you the handle you needed.

Bottom line

Install Reanimated and Moti on day one. Add Skia when the design demands it. Compose freely.

The interesting part of this generalises past animation: the "pick per interaction" rule is roughly how RapidNative decides which primitive to emit for a given screen, since a prompt describing a drag and a prompt describing a fade want different code.


What's your default? I'm curious how many people reach for Reanimated directly on things Moti would handle in three lines, because I did that for about a year.

Top comments (0)