Journal

Tinder swipe card animation in React Native with Reanimated

A swipe card is a pan gesture, a shared value, and an animated style. Reanimated keeps it on the UI thread so it stays smooth even mid-swipe.

Tinder swipe card animation in React Native with Reanimated: a phone toggle icon surrounded by location, calendar, settings, wallet and chart app icons on a coral gradient

TL;DR

A Tinder-style swipe card in React Native uses Reanimated for the animation and Gesture Handler for the drag: a pan gesture updates a shared value, an animated style maps it to position and rotation, and on release the card springs back or flies off and fires a like callback through runOnJS. The animation is short; getting the card design right is the slow part, so starting from a free VP0 design and letting Claude Code or Cursor read its source page is the fastest route to a polished result.

A Tinder-style swipe card in React Native is built with two libraries working together: Reanimated drives the animation on the UI thread, and Gesture Handler reads the drag. A pan gesture updates a shared value, an animated style maps that value to the card’s position and tilt, and on release the card either springs back or flies off the screen. The animation logic is short; getting the card design right is the slower part, which is why starting from a free VP0 design and letting Claude Code or Cursor read its source page is the fastest way to a polished result.

Reanimated, maintained by Software Mansion with more than 10,000 stars, runs the whole gesture on the UI thread through worklets, so the card stays smooth at 60fps, or 120fps on a ProMotion screen, even when the JavaScript thread is busy. That is the reason to use it over a plain Animated approach for something this interactive.

How do you build a Tinder swipe card animation in React Native?

You combine a pan gesture from Gesture Handler with shared values and an animated style from Reanimated. The drag updates a translateX and translateY shared value, an animated style turns those into a transform with a little rotation, and the gesture’s end handler decides whether the card was swiped far enough to count.

The structure is the same every time. Wrap your app in a GestureHandlerRootView, define the shared values, attach a Gesture.Pan() through a GestureDetector, and render the card as an Animated.View. Everything that runs during the drag is a worklet, which is what keeps it on the UI thread. The Reanimated docs explain worklets in depth, but you mostly need to know that gesture callbacks and animated styles run there, and that reaching back into normal JavaScript from inside them requires runOnJS.

Both libraries work in Expo with no native code of your own, so this runs in a managed Expo project as well as a bare React Native one.

The core: shared values, pan gesture, and animated style

The heart of the card is a shared value for position and an animated style that adds rotation as the card moves. This is the part that makes it feel like Tinder rather than a flat drag.

const translateX = useSharedValue(0);
const translateY = useSharedValue(0);

const pan = Gesture.Pan()
  .onUpdate((e) => {
    translateX.value = e.translationX;
    translateY.value = e.translationY;
  })
  .onEnd((e) => {
    if (Math.abs(translateX.value) > SWIPE_THRESHOLD) {
      const dir = Math.sign(translateX.value);
      translateX.value = withSpring(dir * SCREEN_WIDTH * 1.5);
      runOnJS(onSwipe)(dir > 0 ? "like" : "nope");
    } else {
      translateX.value = withSpring(0);
      translateY.value = withSpring(0);
    }
  });

const cardStyle = useAnimatedStyle(() => {
  const rotate = interpolate(
    translateX.value,
    [-SCREEN_WIDTH, 0, SCREEN_WIDTH],
    [-12, 0, 12]
  );
  return {
    transform: [
      { translateX: translateX.value },
      { translateY: translateY.value },
      { rotate: `${rotate}deg` },
    ],
  };
});

The interpolate call is what sells the effect. Mapping horizontal position to a small rotation, around twelve degrees at the screen edge, gives the card the natural tilt as it leaves the stack. Because all of this runs as worklets on the UI thread, the rotation tracks your finger exactly even mid-animation.

Snapping the card: thresholds, spring, and the like callback

On release, the card either commits to a swipe or returns to center, decided by how far it traveled. A threshold of roughly a quarter of the screen width feels right for most cards; below it the card springs home, above it the card flies off in that direction.

The key detail is runOnJS. The gesture handler runs on the UI thread, but advancing your card stack and firing a “like” or “nope” callback happens in normal React state, on the JavaScript thread. Calling that callback directly from inside the worklet crashes or silently fails, so it has to be wrapped in runOnJS, as in the onEnd handler above. This single rule is behind a large share of swipe bugs, and a focused read on fixing Reanimated errors from AI-generated code covers the variations of it.

A distance threshold alone feels stiff, because a fast flick that barely moves the card should still count. Gesture Handler gives you velocityX in the onEnd event, so a better check commits the swipe when either the distance passes the threshold or the velocity is high enough, which matches how a real flick feels. You can also pass that velocity into withSpring as its velocity option so the card leaves the screen carrying the speed of your finger rather than starting from rest.

For the fly-off, withSpring gives a bouncy exit and withTiming gives a faster linear one; most dating-style cards use a quick spring. After the animation, reset the shared values to zero for the next card so the new top card starts centered rather than mid-flight.

Like and nope labels and the card stack

The “LIKE” and “NOPE” labels that fade in as you drag are another interpolate, this time on opacity. Map translateX from zero to the threshold onto an opacity from zero to one, so the label appears as the card commits to a direction.

const likeStyle = useAnimatedStyle(() => ({
  opacity: interpolate(translateX.value, [0, SWIPE_THRESHOLD], [0, 1]),
}));

The stack itself is simpler than it looks. Render only the top two or three cards, with the cards behind scaled down slightly and offset so they peek out. When the top card swipes away, remove it from your data array and the next card becomes the top. Rendering the whole deck at once is the common performance mistake; a deck of two or three visible cards keeps the screen light. A broader set of dating app swipe UI components shows how the stack, labels, and match flow fit together.

Making it smooth with AI builders and a real design

AI builders scaffold the gesture quickly but get the worklet rules wrong in predictable ways. Claude Code and Cursor will produce a plausible Gesture.Pan() and animated style, then call a JavaScript function directly from inside a worklet without runOnJS, or capture a stale value because they treated a shared value like normal state. The code looks right and fails at runtime.

The fix is the same pattern that works elsewhere: give the model a real design and tight rules. Starting from a free VP0 design means the card layout, spacing, and label placement are already decided, so the model implements a real card instead of inventing one, and you spend your time on the gesture logic rather than the visuals. Each VP0 design has a machine-readable source page Claude Code, Cursor, or Rork reads from a pasted link, so the generated card matches a real layout. The animation patterns carry over to other gestures too; a swipe-to-buy button animation, a smooth Reanimated bottom sheet, and the Tinder super like star animation that sits on top of this deck all use the same shared-value and worklet foundation.

For app-style interfaces like a dating or discovery feed, this design-first approach pays off most, because the card is the product and its polish is what users judge.

Common Reanimated swipe bugs

A handful of issues account for most broken swipe cards, and each has a quick fix. The Babel plugin is the first: on older setups, react-native-reanimated/plugin must be the last entry in babel.config.js, and if it is missing or out of order, animations silently do nothing. Recent Expo SDKs include it through babel-preset-expo, so check which setup you are on before adding it twice.

Calling JavaScript from a worklet without runOnJS is the second and most common, usually showing up the moment you wire the like callback. Reading or mutating a shared value on the JavaScript thread, rather than inside a worklet or animated style, is the third, and it produces values that never update. Forgetting GestureHandlerRootView at the root is the fourth, and it makes gestures do nothing at all on some platforms.

Memory issues are the subtler category, often from not resetting shared values or holding references across card transitions. A dedicated walkthrough of a Tinder swipe memory leak in React Native covers how those show up under heavy swiping and how to confirm them.

Should you use a swipe library instead?

A prebuilt library can be the right call, and it is worth weighing before you build from scratch. Packages like react-native-deck-swiper give you a working swipe deck in minutes, which is perfect when you need a standard Tinder-style stack and nothing unusual. They cover the common case well and save real time.

Building it yourself with Reanimated and Gesture Handler wins when you need custom behavior, a non-standard card shape, extra gestures, a specific physics feel, or tight integration with the rest of your animated UI. A library’s defaults are harder to bend than your own code. The honest split is that a library is faster for the standard deck, and a hand-built version is better when the interaction is part of what makes your app distinct. Most teams start with a library and rewrite the deck only when its limits start to bite.

Key takeaways: a Reanimated swipe card that feels right

Use Gesture Handler for the pan and Reanimated for the animation, keep the gesture work in worklets on the UI thread, and wrap any JavaScript callback in runOnJS. Add rotation and label opacity with interpolate, decide swipes with a screen-width threshold, and render only the top few cards in the stack. Let an AI builder scaffold the gesture from a real design, then check it for the worklet mistakes it tends to make. Starting from a free VP0 design gives you a polished card for nothing, while a commissioned swipe feature can run $5,000 or more once design and animation are both accounted for.

You can browse VP0 designs to start your swipe card from a real screen rather than a blank component.

Frequently asked questions

How do you make a Tinder swipe animation in React Native with Reanimated?

Combine a Gesture.Pan() from Gesture Handler with Reanimated shared values and an animated style. The drag updates translateX and translateY, the animated style maps them to a transform plus a small rotation through interpolate, and the gesture’s end handler springs the card back if it did not pass a threshold or flies it off-screen if it did, calling your like or nope callback through runOnJS. Starting the card from a free VP0 design means the layout is settled and you only build the gesture.

What is the safest way to build a swipe card with Claude Code or Cursor?

Constrain the model and review the worklet code. Tell it to keep gesture and animation logic in worklets, to wrap every JavaScript callback in runOnJS, and never to read shared values on the JavaScript thread. AI reliably gets these rules wrong, producing code that compiles but fails at runtime, so a manual pass over the gesture handler is essential. Giving it a real VP0 design to implement keeps the card visuals correct so you can focus on the logic.

Can VP0 provide a free React Native template for a swipe card UI?

Yes. VP0 is a free iOS app design library where every design has a machine-readable source page an AI builder reads from a pasted link, with React Native and SwiftUI variants. You start from the card design, hand its source to Claude Code, Cursor, or Rork, and build the Reanimated gesture on top, rather than designing the card and coding the animation from a blank prompt.

Why use Reanimated instead of the Animated API for swipe cards?

Reanimated runs gestures and animations on the UI thread through worklets, so the card tracks your finger and animates at 60fps, or 120fps on ProMotion displays, even when the JavaScript thread is busy rendering the next card. The built-in Animated API runs more of its work on the JavaScript thread, which causes dropped frames during exactly the kind of continuous gesture a swipe card depends on.

What common errors happen when vibe coding a Reanimated swipe card?

The frequent ones are calling a JavaScript function from a worklet without runOnJS, a missing or misordered Reanimated Babel plugin so animations do nothing, reading shared values on the wrong thread, and forgetting GestureHandlerRootView at the app root. There are also memory issues from not resetting shared values between cards. All of them are quick to fix once you recognize them, but they are easy to ship because the code looks correct.

Other questions VP0 users ask

How do you make a Tinder swipe animation in React Native with Reanimated?

Combine a Gesture.Pan() from Gesture Handler with Reanimated shared values and an animated style. The drag updates translateX and translateY, the animated style maps them to a transform plus a small rotation through interpolate, and the gesture's end handler springs the card back if it did not pass a threshold or flies it off-screen if it did, calling your like or nope callback through runOnJS. Starting the card from a free VP0 design means the layout is settled and you only build the gesture.

What is the safest way to build a swipe card with Claude Code or Cursor?

Constrain the model and review the worklet code. Tell it to keep gesture and animation logic in worklets, to wrap every JavaScript callback in runOnJS, and never to read shared values on the JavaScript thread. AI reliably gets these rules wrong, producing code that compiles but fails at runtime, so a manual pass over the gesture handler is essential. Giving it a real VP0 design to implement keeps the card visuals correct so you can focus on the logic.

Can VP0 provide a free React Native template for a swipe card UI?

Yes. VP0 is a free iOS app design library where every design has a machine-readable source page an AI builder reads from a pasted link, with React Native and SwiftUI variants. You start from the card design, hand its source to Claude Code, Cursor, or Rork, and build the Reanimated gesture on top, rather than designing the card and coding the animation from a blank prompt.

Why use Reanimated instead of the Animated API for swipe cards?

Reanimated runs gestures and animations on the UI thread through worklets, so the card tracks your finger and animates at 60fps, or 120fps on ProMotion displays, even when the JavaScript thread is busy rendering the next card. The built-in Animated API runs more of its work on the JavaScript thread, which causes dropped frames during exactly the kind of continuous gesture a swipe card depends on.

What common errors happen when vibe coding a Reanimated swipe card?

The frequent ones are calling a JavaScript function from a worklet without runOnJS, a missing or misordered Reanimated Babel plugin so animations do nothing, reading shared values on the wrong thread, and forgetting GestureHandlerRootView at the app root. There are also memory issues from not resetting shared values between cards. All of them are quick to fix once you recognize them, but they are easy to ship because the code looks correct.

Part of the React Native & Expo: Mobile Frontend Architecture hub. Browse all VP0 topics →

Keep reading

Voice interrupt animation in React Native: barge-in UI: a glowing iPhone home-screen icon on a purple and blue gradient
Guides 10 min read

Voice interrupt animation in React Native: barge-in UI

Build a voice interrupt (barge-in) animation in React Native with Reanimated. Here are the four states, the audio-reactive orb, and the interrupt logic.

Lawrence Arya · June 10, 2026
Wheel of fortune spinning animation in React Native (Reanimated): the App Store logo as a glossy glass icon on a purple and blue gradient with floating bubbles
Guides 10 min read

Wheel of fortune spinning animation in React Native (Reanimated)

Build a wheel of fortune spin in React Native with SVG and Reanimated. The key: choose the winning segment first, then animate the wheel to land on it.

Lawrence Arya · June 10, 2026
Fix Reanimated Tinder Swipe Card Memory Leaks in RN: the App Store logo on a glass tile over a blue gradient with bubbles
Workflows 5 min read

Fix Reanimated Tinder Swipe Card Memory Leaks in RN

AI-generated Tinder swipe stack leaking memory in React Native? Here is why cards and animation values pile up, and how to clean them so it stays smooth.

Lawrence Arya · June 1, 2026
WHOOP strain gauge chart in React Native with Skia: the App Store logo as a glossy glass icon on a purple and blue gradient with floating bubbles
Guides 10 min read

WHOOP strain gauge chart in React Native with Skia

Build a WHOOP-style strain gauge in React Native with react-native-skia: a gradient arc, rounded caps, a glow, and a sweep animated to the value with Reanimated.

Lawrence Arya · June 10, 2026
Live-Stream Tip Shower Animation in React Native: a glowing iPhone home-screen icon on a purple and blue gradient
Guides 6 min read

Live-Stream Tip Shower Animation in React Native

The gift rain is a business mechanic, not decoration: UI-thread particles, value encoded in spectacle, and a shower that only plays once the payment confirms.

Lawrence Arya · June 7, 2026
Fix Jumping Bottom Sheets in AI Reanimated Code: a vivid neon 3D App Store icon on an orange, pink and blue gradient
Workflows 5 min read

Fix Jumping Bottom Sheets in AI Reanimated Code

AI-generated Reanimated bottom sheet jumping or stuttering? Here is why the gesture and animation fight, and how to make it smooth, from a free template.

Lawrence Arya · June 1, 2026