React Native Gesture Handler 3: Setup, Examples, and Migration

react-native-gesture-handler gives React Native apps native-driven tap, pan,
pinch, rotation, and other touch interactions. The important part in 2026 is
choosing the API that matches the version installed in your project: Gesture
Handler 3 uses hooks such as useTapGesture, while many existing apps and Expo
SDKs still use the Gesture Handler 2 Gesture.* API.
This guide shows the current setup, two complete Gesture Handler 3 examples, and the migration path from the previous API.
Quick answer
- Check the installed versions of React Native, Expo, Gesture Handler, Reanimated, and Worklets.
- Let Expo select compatible versions when you use Expo.
- Wrap the app as close to its root as possible with
GestureHandlerRootView. - For Gesture Handler 3, build gestures with hooks such as
useTapGestureandusePanGesture. - Use Reanimated and Worklets for interactions that must stay smooth on the UI thread.
The official compatibility table
is the source of truth. At the time of this update, Gesture Handler 3 requires
React Native 0.82 or newer, and the 3.2.x line supports React Native 0.83 through
0.87. The versions bundled by Expo may differ, so do not force a major upgrade
over the version selected by expo install.
Choose the right API before changing code
Check the package versions in the app directory:
npm ls react-native react-native-gesture-handler react-native-reanimated react-native-worklets
Use the result to choose the correct path:
| Installed version | API to use |
|---|---|
| Gesture Handler 3.x | Hook API: useTapGesture, usePanGesture, and relation hooks |
| Gesture Handler 2.x | Builder API: Gesture.Tap(), Gesture.Pan(), and Gesture.Simultaneous() |
| Older component API | Plan a staged migration; do not mix it with Gesture Handler 3 relations |
Gesture Handler 3 cannot create relations between hook-based gestures and gestures from the previous API. Migrate one connected gesture group at a time. The official v3 migration guide lists every renamed API and relation.
Install with Expo
Let Expo choose the Gesture Handler version compatible with the current SDK:
npx expo install react-native-gesture-handler
For UI-thread animations, install the Expo-compatible Reanimated and Worklets packages as well:
npx expo install react-native-reanimated react-native-worklets
Current Expo projects configure the Worklets Babel plugin through
babel-preset-expo. If you use a development build and add or change native
dependencies, regenerate and rebuild the native projects:
npx expo prebuild
See the current Expo package pages for Gesture Handler and Reanimated before pinning versions manually.
Install with React Native Community CLI
For a current React Native app using Gesture Handler 3 and Reanimated 4:
npm install react-native-gesture-handler react-native-reanimated react-native-worklets
npx pod-install ios
Reanimated 4 requires the React Native New Architecture. In a Community CLI
project, add the Worklets plugin last in babel.config.js:
module.exports = {
presets: ["module:@react-native/babel-preset"],
plugins: [
// Other Babel plugins go first.
"react-native-worklets/plugin",
],
};
If your app still uses the old architecture, check the Reanimated compatibility documentation before selecting a Reanimated version. Do not upgrade all three libraries independently and hope their native code agrees at build time.
Add GestureHandlerRootView
Every gesture must be below a GestureHandlerRootView. Keep it as close to the
actual application root as possible:
import {GestureHandlerRootView} from 'react-native-gesture-handler';
import {AppNavigator} from './src/navigation/AppNavigator';
export default function App() {
return (
<GestureHandlerRootView style={{flex: 1}}>
<AppNavigator />
</GestureHandlerRootView>
);
}
If gestures inside an Android Modal do not activate, wrap the modal content
in another GestureHandlerRootView. Nested root views are otherwise ignored in
favor of the top-most root.
Tap example with Gesture Handler 3
Gesture Handler 3 replaces Gesture.Tap() with useTapGesture. The callback
runs on the UI thread when Reanimated integration is enabled, so a shared value
can update without waiting for React to render.
import {StyleSheet, Text} from 'react-native';
import {
GestureDetector,
useTapGesture,
} from 'react-native-gesture-handler';
import Animated, {
useAnimatedStyle,
useSharedValue,
withSpring,
} from 'react-native-reanimated';
export function PressableCard() {
const scale = useSharedValue(1);
const tap = useTapGesture({
onBegin: () => {
scale.value = 0.96;
},
onFinalize: () => {
scale.value = withSpring(1);
},
});
const animatedStyle = useAnimatedStyle(() => ({
transform: [{scale: scale.value}],
}));
return (
<GestureDetector gesture={tap}>
<Animated.View style={[styles.card, animatedStyle]}>
<Text style={styles.label}>Tap me</Text>
</Animated.View>
</GestureDetector>
);
}
const styles = StyleSheet.create({
card: {
borderRadius: 16,
backgroundColor: '#17171b',
padding: 20,
},
label: {
color: '#ffffff',
fontWeight: '700',
},
});
Pan example with Gesture Handler 3
For a draggable card, use the accumulated translationX and translationY
values supplied by the pan event:
import {StyleSheet} from 'react-native';
import {
GestureDetector,
usePanGesture,
} from 'react-native-gesture-handler';
import Animated, {
useAnimatedStyle,
useSharedValue,
withSpring,
} from 'react-native-reanimated';
export function DraggableCard() {
const x = useSharedValue(0);
const y = useSharedValue(0);
const pan = usePanGesture({
onUpdate: event => {
x.value = event.translationX;
y.value = event.translationY;
},
onDeactivate: () => {
x.value = withSpring(0);
y.value = withSpring(0);
},
});
const animatedStyle = useAnimatedStyle(() => ({
transform: [
{translateX: x.value},
{translateY: y.value},
],
}));
return (
<GestureDetector gesture={pan}>
<Animated.View style={[styles.card, animatedStyle]} />
</GestureDetector>
);
}
const styles = StyleSheet.create({
card: {
width: 180,
height: 120,
borderRadius: 20,
backgroundColor: '#6558f5',
},
});
Use translationX and translationY when the position should be measured from
the start of the current gesture. Use changeX and changeY when you need the
increment since the previous event frame.
Migrate from Gesture Handler 2 to 3
The old builder API is now documented as the legacy gesture API. The most common replacements are:
| Gesture Handler 2 | Gesture Handler 3 |
|---|---|
Gesture.Tap() | useTapGesture({}) |
Gesture.Pan() | usePanGesture({}) |
Gesture.Simultaneous(a, b) | useSimultaneousGestures(a, b) |
Gesture.Exclusive(a, b) | useExclusiveGestures(a, b) |
gesture.simultaneousWithExternalGesture(other) | simultaneousWith: other |
gesture.requireExternalGestureToFail(other) | requireToFail: other |
gesture.blocksExternalGesture(other) | block: other |
onChange | onUpdate, using changeX or changeY when needed |
Do not combine relation objects from the two APIs. A safe migration sequence is:
- identify one connected set of gestures;
- convert every gesture in that set to hooks;
- replace the composition and cross-component relations;
- test activation, cancellation, and interruption on both iOS and Android;
- migrate the next set only after the first one is stable.
ScrollView, nested gestures, and competing interactions
Gesture relations decide which interaction can activate when a pan, scroll, swipe, or tap overlaps another gesture.
- Use
useSimultaneousGestureswhen both gestures should remain active. - Use
useExclusiveGestureswhen the first successful gesture should win. - Use
requireToFailwhen one gesture must wait for another to fail. - Use
blockwhen one gesture should delay another gesture's activation.
Test the real component hierarchy. A relation that works in an isolated card can behave differently inside a list, native navigator, modal, bottom sheet, or SVG tree.
Performance and accessibility checklist
- Keep gesture callbacks small and deterministic.
- Update shared values directly for animation-driven interactions.
- Move to the JavaScript runtime only when React state or another JS-only API must change.
- Test with Hermes and the React Native JavaScript Inspector rather than legacy remote JS debugging.
- Respect reduced-motion preferences when a gesture triggers a large animation.
- Provide a button or another accessible action when a gesture is the only way to complete an important task.
- Test cancellation, rapid repeated input, multiple pointers, and screen-reader navigation on physical devices.
Troubleshooting
Gestures never activate
Confirm that the gesture is below GestureHandlerRootView, that the native app
was rebuilt after installation, and that Gesture Handler matches the installed
React Native or Expo version.
Reanimated reports a Worklets or Babel error
Install a compatible react-native-worklets version. In a Community CLI app,
keep react-native-worklets/plugin last in the Babel plugins list. Then clear
Metro and rebuild:
npm start -- --reset-cache
The app breaks after forcing Gesture Handler 3 in Expo
Restore the versions selected by npx expo install. Expo's bundled native
modules, Expo Go, and the JavaScript packages must agree. Use a development
build when your project needs a different native package version.
Gestures conflict with ScrollView or a bottom sheet
Model the relationship explicitly instead of increasing activation distances
until the conflict appears to disappear. Use simultaneous, exclusive,
requireToFail, or block relationships, then test both directions and
interruption cases.
Where this belongs in a Dopebase project
Use the React Native dependencies guide to understand the packages and lockfiles shipped with a Dopebase app. Use this article for the general Gesture Handler setup, examples, and migration path. That separation keeps product-specific documentation stable without duplicating the public tutorial.
Next steps
- Read the Gesture Handler getting-started guide.
- Follow the Gesture Handler 3 migration guide before replacing relations.
- Check the Reanimated installation guide for the architecture and Worklets requirements.
- Explore the Dopebase React Native templates when you want to start from an existing app flow instead of an empty project.