react-native-gesture-handler

GitHub

Declarative API exposing platform native touch and gesture system to React Native.

6,773 stars TypeScript #gesture#javascript#react-native
RAW Doc

README (README.md)

<img src="https://user-images.githubusercontent.com/16062886/117444014-2d1ffd80-af39-11eb-9bbb-33c320599d93.png" width="100%" alt="React Native Gesture Handler by Software Mansion">

[](https://swm-delivery.com/www/delivery/ck-slug.php?zoneid=zone-gh-react-native-gesture-handler-1&n=1)
[](https://swm-delivery.com/www/delivery/ck-slug.php?zoneid=zone-gh-react-native-gesture-handler-2&n=1)
[](https://swm-delivery.com/www/delivery/ck-slug.php?zoneid=zone-gh-react-native-gesture-handler-3&n=1)

Declarative API exposing platform native touch and gesture system to React Native.

React Native Gesture Handler provides native-driven gesture management APIs for building best possible touch-based experiences in React Native.

With this library gestures are no longer controlled by the JS responder system, but instead are recognized and tracked in the UI thread.
It makes touch interactions and gesture tracking not only smooth, but also dependable and deterministic.

Installation

Check getting started section of our docs for the detailed installation instructions.

Documentation

Check out our dedicated documentation page for info about this library, API reference and more: https://docs.swmansion.com/react-native-gesture-handler/docs/

Examples

If you want to play with the API but don't feel like trying it on a real app, you can run the example project. Clone the repo, go to the example folder and run:

bash
yarn install

Run yarn start to start the metro bundler

Run yarn android or yarn ios (depending on which platform you want to run the example app on).

You will need to have an Android or iOS device or emulator connected.

React Native Support

react-native-gesture-handler supports the three latest minor releases of react-native.

Gesture Handler 3

Check out our compatibility table in documentation.

IMPORTANT

Minimal supported react-native version for Gesture Handler 3 is 0.82

Gesture Handler 2

| version | react-native version |
| ------- | -------------------- |
| 2.32.0+ | 0.84.0+ |
| 2.28.0+ | 0.79.0+ |
| 2.26.0+ | 0.78.0+ |
| 2.25.0+ | 0.76.0+ |
| 2.24.0+ | 0.75.0+ |
| 2.21.0+ | 0.74.0+ |
| 2.18.0+ | 0.73.0+ |
| 2.16.0+ | 0.68.0+ |
| 2.14.0+ | 0.67.0+ |
| 2.10.0+ | 0.64.0+ |
| 2.0.0+ | 0.63.0+ |

It may be possible to use newer versions of react-native-gesture-handler on React Native with version <= 0.59 by reverse Jetifying.
Read more on that here <https://github.com/mikehardy/jetifier#to-reverse-jetify--convert-node_modules-dependencies-to-support-libraries>

License

Gesture handler library is licensed under The MIT License.

Credits

This project has been build and is maintained thanks to the support from Expo.io and Software Mansion

[](https://expo.io)
[](https://swmansion.com)

Community Discord

Join the Software Mansion Community Discord to chat about Gesture Handler or other Software Mansion libraries.

Gesture Handler is created by Software Mansion

Since 2012 Software Mansion is a software agency with experience in building web and mobile apps. We are Core React Native Contributors and experts in dealing with all kinds of React Native issues. We can help you build your next dream product – Hire us.

---

AGENTS (AGENTS.md)

react-native-gesture-handler library

Project structure

- This project has monorepo structure.
- It contains both, library (/packages/react-native-gesture-handler) and documentation (/packages/docs-gesture-handler).
- It is multiplatform. Library supports android, iOS, web and macos.
- Android codebase is located in /packages/react-native-gesture-handler/android directory. iOS and macos in /packages/react-native-gesture-handler/apple. Web can be found in /packages/react-native-gesture-handler/src/web.
- Some files are platform specific. Those have platform in the file extension, e.g. RNGestureHandlerModule.web.ts

Example apps

- Example apps are located in /apps directory
- basic-example is mostly used to check that android and iOS build correctly.
- expo-example is used to test more advanced examples. Sources are located in apps/common-app.
- macos-example is used to check if library works on macos. Sources are located in apps/common-app.

Packages

- /packages contains documentation and main library package
- This project contains 3 versions of API. The newest is located in packages/react-native-gesture-handler/src/v3 directory. Most of the logic is shared, but make sure that your changes do not break older APIs.
- When writing code, you can use usesNativeOrVirtualDetector function to either include only, or exclude new API v3. It is available on all platforms.

API versions — defaults for new code

- Always use the v3 API by default for any new code, examples, reproductions, or test screens. Gesture. builders (v2) and GestureHandler components (v1) are legacy. Even when a bug is reported against a legacy API, build the reproduction with v3 first to check whether it affects v3 too — switch to the legacy API only if the issue does not reproduce on v3, or the user explicitly asks for a legacy reproduction.
- v3 usage: hook-based gestures (usePanGesture, useTapGesture, useLongPressGesture, …) attached via GestureDetector, all imported from react-native-gesture-handler (the main entry re-exports src/v3).

Build checks

- To check Android build go to apps/basic-example and run yarn android command.
- To check iOS build, go to apps/basic-example and run yarn ios.
- To check macos build, go to apps/macos-example and run yarn macos.
- After any build on macOS/Linux stop the Metro server with for pid in $(lsof -ti :8081); do kill "$pid"; done (this no-ops cleanly when nothing is listening on port 8081; pkill -f "metro" is not sufficient)

JavaScript checks

- To run TypeScript checks use yarn ts-check command. You can run it directly in packages/react-native-gesture-handler if working on package, or from root of the repository.
- To run Jest tests, use yarn test command in packages/react-native-gesture-handler. You can also pass filename to run tests from specific file.
- To run eslint check, use yarn lint:js

Formatting

- To format code use use yarn format:{apple | android | js}. apple works for both, iOS and macos.

---

Package.Json (package.json)

{
"name": "react-native-gesture-handler-monorepo",
"version": "0.0.0",
"private": true,
"workspaces": [
"packages/react-native-gesture-handler",
"apps/basic-example",
"apps/expo-example",
"apps/macos-example",
"apps/common-app"
],
"scripts": {
"postinstall": "yarn build",
"build": "husky install && yarn workspaces foreach --all --parallel --topological-dev run build",
"ts-check": "yarn workspaces foreach --all --parallel --topological-dev run ts-check",
"lint-js": "yarn workspaces foreach --all --parallel --topological-dev run lint-js",
"format-js": "yarn workspaces foreach --all --parallel --topological-dev run format-js",
"clean": "yarn workspaces foreach --all --parallel --topological-dev run clean && rm -rf node_modules yarn.lock"
},
"devDependencies": {
"@types/react": "^19.0.12",
"@typescript-eslint/eslint-plugin": "^6.9.0",
"@typescript-eslint/parser": "^6.9.0",
"@yarnpkg/types": "^4.0.1",
"eslint": "^8.57.0",
"eslint-config-satya164": "3.3.0",
"eslint-import-resolver-babel-module": "^5.2.0",
"eslint-plugin-jest": "27.4.3",
"eslint-plugin-prettier": "^5.0.1",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-simple-import-sort": "^13.0.0",
"husky": "^8.0.1",
"jest": "^29.7.0",
"lint-staged": "^12.3.2",
"prettier": "3.3.3",
"typescript": "~6.0.3"
},
"packageManager": "[email protected]",
"resolutions": {
"expo-modules-core@npm:~57.0.10": "patch:expo-modules-core@npm%3A57.0.10#~/.yarn/patches/expo-modules-core-npm-57.0.10-1d55439aaa.patch"
}
}

---

Rnrepo.Config.Json (rnrepo.config.json)

{
"denyList": {
"android": ["react-native-gesture-handler"],
"ios": ["react-native-gesture-handler"]
}
}

---

Tsconfig.Json (tsconfig.json)

{
"compilerOptions": {
"esModuleInterop": true,
"jsx": "react-native",
"lib": ["esnext", "dom"],
"types": ["jest"],
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"skipLibCheck": true,
"strict": true,
"target": "esnext",
"declaration": true,
"declarationMap": false,
"noStrictGenericChecks": false,
"forceConsistentCasingInFileNames": true,
"noImplicitUseStrict": false,
"noImplicitOverride": true,
"noUnusedParameters": true,
"noUnusedLocals": true
}
}

---

.Eslintrc.Json (.eslintrc.json)

{
"root": true,
"extends": [
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking",
"satya164"
],
"settings": {
"react": { "version": "19" },
"import/resolver": {
"babel-module": {}
}
},
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": ["./tsconfig.json"]
},
"env": { "browser": true, "node": true, "jest/globals": true },
"plugins": ["react", "jest", "simple-import-sort"],
"ignorePatterns": [
"packages/react-native-gesture-handler/lib//*",
"/*.config.js",
"scripts//*.js",
"/node_modules//*"
],
"rules": {
// removed in new jest-eslint-plugin, referenced in satya config
"jest/no-truthy-falsy": "off",
"jest/valid-describe": "off",

"jest/expect-expect": [
"warn",
{ "assertFunctionNames": ["expect", "assert"] }
],
"jest/no-conditional-expect": "warn",

// temporary, remove after we type internals better
"@typescript-eslint/restrict-template-expressions": "warn",
"@typescript-eslint/no-unsafe-member-access": "warn",
"@typescript-eslint/no-unsafe-call": "warn",
"@typescript-eslint/no-unsafe-assignment": "warn",
"@typescript-eslint/no-unsafe-argument": "warn",
"@typescript-eslint/no-unsafe-return": "warn",
"@typescript-eslint/no-non-null-assertion": "warn",
"@typescript-eslint/ban-types": "warn",
"@typescript-eslint/consistent-type-imports": "error",
"@typescript-eslint/consistent-type-exports": "error",
"@typescript-eslint/no-unsafe-enum-comparison": "off",

// common
"@typescript-eslint/explicit-module-boundary-types": "off",
"import/named": "off",
"react/sort-comp": "off",
"react/no-unused-prop-types": "warn",
"react-hooks/exhaustive-deps": "warn",
"@typescript-eslint/no-floating-promises": "warn",
"@eslint-react/no-missing-component-display-name": "warn",
"@eslint-react/no-nested-components": "warn",
"@eslint-react/no-nested-component-definitions": "warn",
"prefer-const": [
"error",
{
"destructuring": "all"
}
],
"@typescript-eslint/no-unused-vars": [
"error",
{ "argsIgnorePattern": "^_" }
],
"@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/no-redundant-type-constituents": "warn",
"@typescript-eslint/no-empty-function": "error",
"@typescript-eslint/no-misused-promises": "warn",
"@eslint-react/no-array-index-key": "warn",
"@eslint-react/hooks-extra/no-direct-set-state-in-use-effect": "warn",
"@eslint-react/hooks-extra/prefer-use-state-lazy-initialization": "warn",
"@eslint-react/hooks-extra/ensure-custom-hooks-using-other-hooks": "warn",
"no-redeclare": "off",
"@typescript-eslint/no-redeclare": "error",
"no-use-before-define": "off",
"@typescript-eslint/no-use-before-define": "error",
"@typescript-eslint/ban-ts-comment": [
"error",
{
"ts-ignore": "allow-with-description",
"ts-expect-error": "allow-with-description"
}
],
"curly": "error",
"spaced-comment": "error",
"no-alert": "warn",
"simple-import-sort/imports": "error",
"simple-import-sort/exports": "error"
},
"overrides": [
{
"files": [
"packages/react-native-gesture-handler/src/v3/",
"packages/react-native-gesture-handler/src/web/",
"packages/react-native-gesture-handler/src/*.ts"
],
"rules": {
"no-restricted-globals": [
"error",
{
"name": "global",
"message": "Use the standard globalThis — the bare global binding is React Native/Node-specific and doesn't exist in browsers."
}
],
"no-restricted-syntax": [
"error",
{
"selector": "Identifier[name='__DEV__']:not(:function *)",
"message": "Don't read __DEV__ at module scope — the global may not be defined yet outside Metro when modules are evaluated. Read it lazily inside a function instead."
}
]
}
},
{
"files": ["packages/react-native-gesture-handler/src/web/"],
"rules": {
"no-restricted-imports": [
"error",
{
"paths": [
{
"name": "react-native",
"message": "The web handlers are platform-agnostic engine code — they must not depend on react-native. Guard on runtime capabilities (e.g. DOM availability) instead."
}
]
}
]
}
}
]
}

---

.Lintstagedrc.Json (.lintstagedrc.json)

{
"{apps/basic-example,packages/react-native-gesture-handler}/package.json": "node scripts/check-rn-versions.js",
"/*.{ts,tsx}": ["prettier --write", "eslint"],
"packages/react-native-gesture-handler/android//*.kt": [
"node scripts/check-android-dirs.js",
"yarn format:android"
],
"packages/react-native-gesture-handler/apple//*.{h,m,mm,cpp}": "yarn format:apple",
"packages/react-native-gesture-handler/{shared,android/src}//*.{h,cpp}": "yarn format:cpp"
}

---

.Prettierrc.Json (.prettierrc.json)

{
"bracketSameLine": true,
"printWidth": 80,
"tabWidth": 2,
"semi": true,
"singleQuote": true,
"trailingComma": "es5"
}

---

.Yarnrc.Yml (.yarnrc.yml)

nodeLinker: node-modules

yarnPath: .yarn/releases/yarn-4.13.0.cjs

---

Apps/Basic Example/README (apps/basic-example/README.md)

Basic Gesture Handler example app

This app shows basic capabilities of Gesture Handler. It doesn't depend on any other package - this makes it easier to confirm that Gesture Handler works correctly.

Installing & running application

Before running application you need to install all dependencies. To do that:

- In project's root directory run yarn

Android

You can run this application by yarn android or from Android Studio.

iOS

To run on iOS first go to BasicExample/ios and run pod install.

Then in BasicExample run yarn ios or run application from Xcode.

---

Apps/Basic Example/App.Json (apps/basic-example/app.json)

{
"name": "BasicExample",
"displayName": "BasicExample"
}

---

Apps/Basic Example/Package.Json (apps/basic-example/package.json)

{
"name": "basic-example",
"version": "0.0.1",
"private": true,
"scripts": {
"android": "react-native run-android",
"ios": "react-native run-ios",
"lint": "eslint .",
"start": "react-native start",
"test": "jest",
"ts-check": "yarn tsc --noEmit",
"lint-js": "eslint --ext '.js,.ts,.tsx' src/ && yarn prettier --check './src//*.{js,jsx,ts,tsx}'",
"format-js": "prettier --write --list-different './src//*.{js,jsx,ts,tsx}'",
"clean-android": "rm -rf android/.gradle android/.kotlin android/build",
"clean-ios": "rm -rf ios/build ios/Pods ios/Podfile.lock",
"clean": "rm -rf node_modules && yarn clean-android && yarn clean-ios",
"pods": "cd ios && bundle install && bundle exec pod install"
},
"dependencies": {
"react": "19.2.3",
"react-native": "0.87.0",
"react-native-gesture-handler": "workspace:*",
"react-native-reanimated": "4.6.0-nightly-20260811-248dee712",
"react-native-worklets": "patch:react-native-worklets@npm%3A0.12.0-nightly-20260810-fb9cb5596#~/.yarn/patches/react-native-worklets-npm-0.12.0-nightly-20260810-fb9cb5596-f08a72b88e.patch"
},
"devDependencies": {
"@babel/core": "^7.25.2",
"@babel/preset-env": "^7.25.3",
"@babel/runtime": "^7.25.0",
"@react-native-community/cli": "20.2.0",
"@react-native-community/cli-platform-android": "20.2.0",
"@react-native-community/cli-platform-ios": "20.2.0",
"@react-native/babel-preset": "0.87.0",
"@react-native/eslint-config": "0.87.0",
"@react-native/jest-preset": "0.87.0",
"@react-native/metro-config": "0.87.0",
"@react-native/typescript-config": "0.87.0",
"@rnrepo/build-tools": "~0.1.3-beta.0",
"@types/jest": "^29.5.13",
"@types/react": "^19.2.0",
"@types/react-test-renderer": "^19.1.0",
"@typescript-eslint/eslint-plugin": "^6.9.0",
"@typescript-eslint/parser": "^6.9.0",
"eslint": "^8.57.0",
"eslint-config-satya164": "3.3.0",
"eslint-import-resolver-babel-module": "^5.2.0",
"eslint-plugin-jest": "27.4.3",
"eslint-plugin-prettier": "^5.0.1",
"eslint-plugin-react": "^7.37.5",
"jest": "^29.6.3",
"prettier": "3.3.3",
"react-test-renderer": "19.2.3",
"typescript": "~6.0.3"
},
"engines": {
"node": ">=22.11.0"
},
"installConfig": {
"selfReferences": false
}
}

---

Apps/Basic Example/Tsconfig.Json (apps/basic-example/tsconfig.json)

{
"extends": "../../tsconfig.json",
"compilerOptions": {
"ignoreDeprecations": "6.0",
"noEmit": true,
"paths": {
"react-native-gesture-handler": [
"../../packages/react-native-gesture-handler/src"
],
"react-native-gesture-handler/ReanimatedSwipeable": [
"../../packages/react-native-gesture-handler/src/components/ReanimatedSwipeable/"
],
"react-native-gesture-handler/ReanimatedDrawerLayout": [
"../../packages/react-native-gesture-handler/src/components/ReanimatedDrawerLayout.tsx"
],
"react-native-gesture-handler/jest-utils": [
"../../packages/react-native-gesture-handler/src/jestUtils/index.ts"
]
},
"types": ["../../packages/react-native-gesture-handler/src/global.d.ts"]
},
"include": ["src//.ts", "src//.tsx"]
}

---

Apps/Basic Example/Ios/BasicExample/Images.Xcassets/Contents.Json (apps/basic-example/ios/BasicExample/Images.xcassets/Contents.json)

{
"info" : {
"version" : 1,
"author" : "xcode"
}
}

---

Apps/Basic Example/Ios/BasicExample/Images.Xcassets/AppIcon.Appiconset/Contents.Json (apps/basic-example/ios/BasicExample/Images.xcassets/AppIcon.appiconset/Contents.json)

{
"images" : [
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "20x20"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "20x20"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "29x29"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "29x29"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "40x40"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "40x40"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "60x60"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "60x60"
},
{
"idiom" : "ios-marketing",
"scale" : "1x",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

---

Apps/Common App/README (apps/common-app/README.md)

Gesture Handler example app

This is project contains source code for examples shared betwwen example apps.

---

Apps/Common App/Package.Json (apps/common-app/package.json)

{
"name": "common-app",
"version": "0.0.1",
"private": true,
"scripts": {
"android": "react-native run-android",
"ios": "react-native run-ios",
"lint": "eslint .",
"start": "react-native start",
"test": "jest",
"ts-check": "yarn tsc --noEmit",
"lint-js": "eslint --ext '.js,.ts,.tsx' src/ && yarn prettier --check './src//*.{js,jsx,ts,tsx}'",
"format-js": "prettier --write --list-different App.tsx './src//*.{js,jsx,ts,tsx}'"
},
"peerDependencies": {
"react": "*",
"react-native": "*"
},
"dependencies": {
"@react-native-async-storage/async-storage": "^2.1.2",
"@react-navigation/elements": "^2.3.8",
"@react-navigation/native": "^7.1.6",
"@react-navigation/stack": "^7.2.10",
"@swmansion/icons": "^0.0.1",
"react-native-gesture-handler": "workspace:*",
"react-native-pager-view": "^8.0.2",
"react-native-reanimated": "4.6.0-nightly-20260811-248dee712",
"react-native-safe-area-context": "~5.7.0",
"react-native-screens": "4.25.2",
"react-native-svg": "15.15.4",
"react-native-worklets": "patch:react-native-worklets@npm%3A0.12.0-nightly-20260810-fb9cb5596#~/.yarn/patches/react-native-worklets-npm-0.12.0-nightly-20260810-fb9cb5596-f08a72b88e.patch"
},
"devDependencies": {
"@babel/core": "^7.25.2",
"@babel/preset-env": "^7.25.3",
"@babel/runtime": "^7.25.0",
"@react-native-community/cli": "18.0.1",
"@react-native-community/cli-platform-android": "18.0.0",
"@react-native-community/cli-platform-ios": "18.0.0",
"@react-native/babel-preset": "0.79.0",
"@react-native/eslint-config": "0.79.0",
"@react-native/metro-config": "0.79.0",
"@react-native/typescript-config": "0.79.0",
"@types/jest": "^29.5.13",
"@types/react": "^19.0.12",
"@types/react-test-renderer": "^19.0.0",
"@typescript-eslint/eslint-plugin": "^6.9.0",
"@typescript-eslint/parser": "^6.9.0",
"eslint": "^8.57.0",
"eslint-config-satya164": "3.3.0",
"eslint-import-resolver-babel-module": "^5.2.0",
"eslint-plugin-jest": "27.4.3",
"eslint-plugin-prettier": "^5.0.1",
"eslint-plugin-react": "^7.37.5",
"expo-camera": "~16.1.1",
"jest": "^29.6.3",
"prettier": "3.3.3",
"react-test-renderer": "19.0.0",
"typescript": "~6.0.3"
},
"engines": {
"node": ">=18"
}
}

---

Apps/Common App/Tsconfig.Json (apps/common-app/tsconfig.json)

{
"extends": "../../tsconfig.json",
"compilerOptions": {
"noEmit": true,
"declaration": false,
"noImplicitOverride": false,
"paths": {
"@/": ["./"],
"react-native-gesture-handler": [
"../../packages/react-native-gesture-handler/src"
],
"react-native-gesture-handler/ReanimatedSwipeable": [
"../../packages/react-native-gesture-handler/src/components/ReanimatedSwipeable/"
],
"react-native-gesture-handler/ReanimatedDrawerLayout": [
"../../packages/react-native-gesture-handler/src/components/ReanimatedDrawerLayout.tsx"
],
"react-native-gesture-handler/jest-utils": [
"../../packages/react-native-gesture-handler/src/jestUtils/index.ts"
]
},
"types": ["../../packages/react-native-gesture-handler/src/global.d.ts"]
},
"include": ["src//.ts", "src//.tsx", "index.ts"]
}

---

Apps/Expo Example/README (apps/expo-example/README.md)

Gesture Handler Expo example app

This is an example application demonstrating the functionality of the react-native-gesture-handler library with Expo.

Installing & running application

Before running the application, you need to install all dependencies. To do that:

- In the project's root directory, run yarn.

Next run npx expo prebuild.

Android

Run this application by yarn android or from Android Studio.

iOS

Run yarn ios or run application from Xcode.

Web

Run yarn web. Alternatively run yarn start and in browser navigate to localhost:8081

---

Apps/Expo Example/Package.Json (apps/expo-example/package.json)

{
"name": "expo-example",
"version": "1.0.0",
"main": "index.ts",
"scripts": {
"postinstall": "npx expo prebuild",
"start": "expo start --dev-client",
"android": "expo run:android",
"ios": "expo run:ios",
"web": "expo start --web",
"clean": "rm -rf node_modules android ios"
},
"dependencies": {
"@expo/metro-runtime": "~57.0.9",
"@react-native-async-storage/async-storage": "2.2.0",
"@react-navigation/elements": "^2.3.8",
"@react-navigation/native": "^7.1.6",
"@react-navigation/stack": "^7.2.10",
"@swmansion/icons": "^0.0.1",
"common-app": "workspace:*",
"expo": "^57.0.0",
"expo-camera": "~57.0.3",
"expo-status-bar": "~57.0.1",
"react": "19.2.3",
"react-dom": "19.2.3",
"react-native": "0.86.2",
"react-native-gesture-handler": "workspace:*",
"react-native-pager-view": "^8.0.2",
"react-native-reanimated": "4.6.0-nightly-20260811-248dee712",
"react-native-safe-area-context": "~5.7.0",
"react-native-screens": "4.25.2",
"react-native-svg": "15.15.4",
"react-native-web": "^0.21.0",
"react-native-worklets": "patch:react-native-worklets@npm%3A0.12.0-nightly-20260810-fb9cb5596#~/.yarn/patches/react-native-worklets-npm-0.12.0-nightly-20260810-fb9cb5596-f08a72b88e.patch"
},
"devDependencies": {
"@babel/core": "^7.25.2",
"@rnrepo/expo-config-plugin": "0.3.0-beta.3",
"@types/react": "~19.1.10",
"@types/react-dom": "~19.1.7",
"@types/react-native-web": "^0",
"typescript": "~6.0.3"
},
"private": true,
"installConfig": {
"hoistingLimits": "workspaces",
"selfReferences": false
}
}

---

Apps/Expo Example/Rnrepo.Config.Json (apps/expo-example/rnrepo.config.json)

{
"denyList": {
"android": ["react-native-gesture-handler"],
"ios": ["react-native-gesture-handler"]
}
}

---

Apps/Expo Example/Tsconfig.Json (apps/expo-example/tsconfig.json)

{
"extends": "../../tsconfig.json",
"compilerOptions": {
"noEmit": true,
"baseUrl": ".",
"paths": {
"common-app": ["../common-app/index.ts"],
"react-native-gesture-handler": [
"../../packages/react-native-gesture-handler/src"
],
"react-native-gesture-handler/ReanimatedSwipeable": [
"../../packages/react-native-gesture-handler/src/components/ReanimatedSwipeable/"
],
"react-native-gesture-handler/ReanimatedDrawerLayout": [
"../../packages/react-native-gesture-handler/src/components/ReanimatedDrawerLayout.tsx"
],
"react-native-gesture-handler/jest-utils": [
"../../packages/react-native-gesture-handler/src/jestUtils/index.ts"
]
},
"types": ["../../packages/react-native-gesture-handler/src/global.d.ts"]

},
"include": ["src//.ts", "src//.tsx", "App.tsx"],
"exclude": ["metro.config.js", "android", "ios", ".bundle", "node_modules"]
}

---

Apps/Macos Example/README (apps/macos-example/README.md)

macOS Gesture Handler example app

This is an example application demonstrating the functionality of the react-native-gesture-handler library on macOS.

Installing & running application

Before running the application, you need to install all dependencies. To do that:

- In the project's root directory, run yarn.
- Navigate to the MacOSExample/macos directory and run bundle install && bundle exec pod install
- Navigate to the MacOSExample and run yarn start to start the Metro bundler.
- Open the MacOSExample project in Xcode and build the app to run it on a macOS simulator or device.

---

Apps/Macos Example/App.Json (apps/macos-example/app.json)

{
"name": "MacOSExample",
"displayName": "MacOSExample"
}

---

Apps/Macos Example/Package.Json (apps/macos-example/package.json)

{
"name": "macos-example",
"version": "0.0.1",
"private": true,
"scripts": {
"macos": "npx react-native-macos run-macos",
"lint": "eslint .",
"start": "react-native start",
"test": "jest",
"clean": "rm -rf node_modules && cd macos && rm -rf build Pods Podfile.lock",
"pods": "cd macos && bundle install && bundle exec pod install"
},
"dependencies": {
"@react-native-async-storage/async-storage": "2.1.2",
"@react-navigation/elements": "^2.3.8",
"@react-navigation/native": "^7.1.6",
"@react-navigation/stack": "^7.2.10",
"@swmansion/icons": "^0.0.1",
"common-app": "workspace:*",
"react": "19.1.4",
"react-native": "0.81.2",
"react-native-gesture-handler": "workspace:*",
"react-native-macos": "0.81.2",
"react-native-reanimated": "~4.3.1",
"react-native-safe-area-context": "~5.7.0",
"react-native-screens": "4.25.2",
"react-native-svg": "15.15.4",
"react-native-worklets": "0.8.1"
},
"devDependencies": {
"@babel/core": "^7.25.2",
"@babel/preset-env": "^7.25.3",
"@babel/runtime": "^7.25.0",
"@react-native-community/cli": "20.0.0",
"@react-native-community/cli-platform-android": "20.0.0",
"@react-native-community/cli-platform-ios": "20.0.0",
"@react-native/babel-preset": "0.81.2",
"@react-native/eslint-config": "0.81.2",
"@react-native/metro-config": "0.81.2",
"@react-native/typescript-config": "0.81.2",
"@types/jest": "^29.5.13",
"@types/react": "^19.1.4",
"@types/react-test-renderer": "^19.1.0",
"jest": "^29.6.3",
"react-test-renderer": "19.1.4",
"typescript": "~6.0.3"
},
"engines": {
"node": ">=20"
},
"installConfig": {
"hoistingLimits": "workspaces",
"selfReferences": false
}
}

---

Apps/Macos Example/Tsconfig.Json (apps/macos-example/tsconfig.json)

{
"extends": "../../tsconfig.json",
"compilerOptions": {
"noEmit": true,
"baseUrl": ".",
"paths": {
"common-app": ["../common-app/index.ts"],
"react-native-gesture-handler": [
"../../packages/react-native-gesture-handler/src"
],
"react-native-gesture-handler/ReanimatedSwipeable": [
"../../packages/react-native-gesture-handler/src/components/ReanimatedSwipeable/"
],
"react-native-gesture-handler/ReanimatedDrawerLayout": [
"../../packages/react-native-gesture-handler/src/components/ReanimatedDrawerLayout.tsx"
],
"react-native-gesture-handler/jest-utils": [
"../../packages/react-native-gesture-handler/src/jestUtils/index.ts"
]
},
"types": ["../../packages/react-native-gesture-handler/src/global.d.ts"]
},
"include": ["src//.ts", "src//.tsx", "index.ts"],
"exclude": ["metro.config.js", "macos", ".bundle", "node_modules", "/Pods"]
}

---

Apps/Macos Example/Macos/MacOSExample MacOS/Assets.Xcassets/Contents.Json (apps/macos-example/macos/MacOSExample-macOS/Assets.xcassets/Contents.json)

{
"info" : {
"author" : "xcode",
"version" : 1
}
}

---

Apps/Macos Example/Macos/MacOSExample MacOS/Assets.Xcassets/AppIcon.Appiconset/Contents.Json (apps/macos-example/macos/MacOSExample-macOS/Assets.xcassets/AppIcon.appiconset/Contents.json)

{
"images" : [
{
"idiom" : "mac",
"scale" : "1x",
"size" : "16x16"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "16x16"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "32x32"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "32x32"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "128x128"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "128x128"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "256x256"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "256x256"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "512x512"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "512x512"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

---

Packages/Docs Gesture Handler/README (packages/docs-gesture-handler/README.md)

Website

This website is built using Docusaurus 2, a modern static website generator.

Installation

console
yarn install

Local Development

console
yarn start

This command starts a local development server and open up a browser window. Most changes are reflected live without having to restart the server.

Build

console
yarn build

This command generates static content into the build directory and can be served using any static contents hosting service.

Deployment

console
GIT_USER=<Your GitHub username> USE_SSH=true yarn deploy

If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the gh-pages branch.

---

Packages/Docs Gesture Handler/Package.Json (packages/docs-gesture-handler/package.json)

{
"name": "react-native-gesture-handler-docs",
"version": "0.0.0",
"private": true,
"scripts": {
"docusaurus": "docusaurus",
"start": "docusaurus start",
"build": "docusaurus build && node -r esbuild-register scripts/build-og-images.jsx",
"swizzle": "docusaurus swizzle",
"deploy": "docusaurus deploy",
"clear": "docusaurus clear",
"serve": "docusaurus serve",
"write-translations": "docusaurus write-translations",
"write-heading-ids": "docusaurus write-heading-ids",
"typecheck": "tsc --noEmit",
"lint": "prettier --check docs src versioned_docs versioned_docs",
"format": "prettier --write --list-different docs src versioned_docs versioned_docs"
},
"dependencies": {
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-proposal-export-namespace-from": "^7.18.9",
"@babel/preset-env": "^7.24.4",
"@babel/preset-react": "^7.18.6",
"@babel/preset-typescript": "^7.21.0",
"@docusaurus/core": "3.9.2",
"@docusaurus/plugin-client-redirects": "3.9.2",
"@docusaurus/plugin-debug": "3.9.2",
"@docusaurus/plugin-google-tag-manager": "3.9.2",
"@docusaurus/preset-classic": "3.9.2",
"@docusaurus/theme-common": "3.9.2",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
"@mdx-js/react": "^3.0.0",
"@mui/material": "^7.1.0",
"@swmansion/t-rex-ui": "1.3.7",
"@vercel/og": "^0.6.2",
"babel-polyfill": "^6.26.0",
"babel-preset-expo": "^9.2.2",
"babel-preset-react-native": "^4.0.1",
"clsx": "^2.1.0",
"copy-text-to-clipboard": "3.2.2",
"eslint-plugin-simple-import-sort": "^13.0.0",
"prism-react-renderer": "^2.1.0",
"raf": "^3.4.1",
"raw-loader": "^4.0.2",
"react": "19.1.1",
"react-colorful": "^5.6.1",
"react-dom": "19.1.1",
"react-draggable": "^4.4.5",
"react-native": "0.83.0",
"react-native-gesture-handler": "3.1.0",
"react-native-reanimated": "4.3.0",
"react-native-web": "0.21.2",
"react-native-worklets": "0.8.1",
"source-map": "^0.7.4",
"source-map-loader": "^4.0.1",
"usehooks-ts": "^2.9.1"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "3.9.2",
"@docusaurus/tsconfig": "3.9.2",
"copy-webpack-plugin": "^11.0.0",
"esbuild": "^0.28.1",
"esbuild-register": "^3.5.0",
"eslint-plugin-mdx": "^2.2.0",
"prettier": "^3.3.3",
"typescript": "~5.2.2",
"webpack-cli": "^7.0.2"
},
"browserslist": {
"production": [
">0.5%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"engines": {
"node": ">=18.0"
},
"resolutions": {
"webpackbar": "^7.0.0"
},
"packageManager": "[email protected]"
}

---

Packages/Docs Gesture Handler/Redirects.Json (packages/docs-gesture-handler/redirects.json)

{
"redirects": [
{
"to": "/docs/fundamentals/getting-started",
"from": "/docs/"
},
{
"to": "/docs/fundamentals/getting-started",
"from": "/docs/fundamentals/introduction"
},
{
"to": "/docs/fundamentals/getting-started",
"from": "/docs/fundamentals/installation"
},
{
"to": "/docs/legacy-gestures/pan-gesture",
"from": "/docs/gestures/pan-gesture"
},
{
"to": "/docs/legacy-gestures/tap-gesture",
"from": "/docs/gestures/tap-gesture"
},
{
"to": "/docs/legacy-gestures/long-press-gesture",
"from": "/docs/gestures/long-press-gesture"
},
{
"to": "/docs/legacy-gestures/rotation-gesture",
"from": "/docs/gestures/rotation-gesture"
},
{
"to": "/docs/legacy-gestures/pinch-gesture",
"from": "/docs/gestures/pinch-gesture"
},
{
"to": "/docs/legacy-gestures/fling-gesture",
"from": "/docs/gestures/fling-gesture"
},
{
"to": "/docs/legacy-gestures/hover-gesture",
"from": "/docs/gestures/hover-gesture"
},
{
"to": "/docs/legacy-gestures/native-gesture",
"from": "/docs/gestures/native-gesture"
},
{
"to": "/docs/legacy-gestures/manual-gesture",
"from": "/docs/gestures/manual-gesture"
},
{
"to": "/docs/legacy-gestures/force-touch-gesture",
"from": "/docs/gestures/force-touch-gesture"
},
{
"to": "/docs/2.x/guides/upgrading-to-2",
"from": "/docs/guides/upgrading-to-2"
},
{
"to": "/docs/core-components/gesture-detectors",
"from": "/docs/gestures/gesture-detector"
},
{
"to": "/docs/core-components/gesture-detectors",
"from": "/docs/fundamentals/gesture-detectors"
},
{
"to": "/docs/core-components/root-view",
"from": "/docs/fundamentals/root-view"
},
{
"to": "/docs/fundamentals/gesture-animation",
"from": "/docs/guides/quickstart"
},
{
"to": "/docs/gestures/state-manager",
"from": "/docs/fundamentals/state-manager"
}
]
}

---

Packages/Docs Gesture Handler/Tsconfig.Json (packages/docs-gesture-handler/tsconfig.json)

{
// This file is not used in compilation. It is here just for a nice editor experience.
"extends": "@docusaurus/tsconfig",
"compilerOptions": {
"baseUrl": "."
}
}

---

Packages/Docs Gesture Handler/Docs/Components/ Category .Json (packages/docs-gesture-handler/docs/components/_category_.json)

{
"label": "Components",
"position": 6,
"link": {
"type": "generated-index"
}
}

---

Packages/Docs Gesture Handler/Docs/Components/Buttons (packages/docs-gesture-handler/docs/components/buttons.mdx)

---
id: buttons
title: Buttons
sidebar_label: Buttons
sidebar_position: 6
---

import useBaseUrl from '@docusaurus/useBaseUrl';
import GifGallery from '@site/components/GifGallery';

:::danger
Button components described in this section are deprecated and will be removed in the future. Please use Touchable instead.
:::

<GifGallery>
<img src={useBaseUrl('gifs/samplebutton.gif')} width="280" />
</GifGallery>

The Gesture Handler library offers native components that function as buttons, serving as alternatives to TouchableHighlight or TouchableOpacity from the core React Native framework. These buttons process touch recognition natively, which ensures a deterministic response. This capability significantly enhances performance; for example, it allows for immediate ripple effects on Android, unlike TouchableNativeFeedback, which requires a touch event roundtrip to JavaScript that can cause delays, especially noticeable on older devices. Additionally, these components handle default platform interactions natively, particularly in scrollable containers where interactions are smartly delayed to prevent unintended highlighting during a fling.

Gesture Handler library exposes components that render native touchable elements under the hood:

- RawButton
- BaseButton
- RectButton
- BorderlessButton

On top of that all the buttons are wrapped with Native gesture and therefore allow for all its properties to be applied to them.


RawButton

The most basic button component does not provide any feedback and lacks props such as onPress. It serves as a foundation for other button components and is ideal if you wish to implement your own custom interactions for when the button is pressed.

RawButton accepts all Native gesture props, accessibility props, along with the following additional properties:

exclusive

ts
exclusive?: boolean;

Defines whether pressing this button prevents other buttons exported by Gesture Handler from being pressed. By default set to true.

<Badges platforms={['android']}>

rippleColor [A]


</Badges>

ts
rippleColor?: number | ColorValue | null;

Defines color of native ripple animation.

<Badges platforms={['android']}>

rippleRadius [A]


</Badges>


ts
rippleRadius?: number | null;

Defines radius of native ripple animation.

<Badges platforms={['android']}>

borderless [A]


</Badges>

ts
borderless?: boolean;

If set to false, ripple animation will render only within view bounds.

<Badges platforms={['android']}>

foreground [A]


</Badges>

ts
foreground?: boolean;

Defines whether the ripple animation should be drawn on the foreground of the view.

<Badges platforms={['android']}>

touchSoundDisabled [A]


</Badges>

ts
touchSoundDisabled?: boolean;

If set to true, the system will not play a sound when the button is pressed.

BaseButton

Can be used as a base class if you'd like to implement some custom interaction for when the button is pressed. It has all the props of RawButton and in addition to that it also provides the following props:

onPress

ts
onPress?: (pointerInside: boolean) => void;

Triggered when the button gets pressed (analogous to onPress in TouchableHighlight from RN core).


onLongPress

ts
onLongPress?: () => void;

Triggered when the button gets pressed for at least delayLongPress milliseconds.


onActiveStateChange

ts
onActiveStateChange?: (active: boolean) => void;

Triggered when the button transitions between active and inactive states. It passes the current active state as a boolean variable to the method as the first parameter.

delayLongPress

ts
delayLongPress?: number;

Defines the delay, in milliseconds, after which the onLongPress callback gets called. By default set to 600.

RectButton

This type of button component is ideal for use with rectangular elements or content blocks that can be pressed, such as table rows or buttons featuring text and icons. It ensures platform-specific interactions, such as rendering a rectangular ripple on Android or highlighting the background on iOS and older Android versions. In addition to the props offered by BaseButton, it accepts the following:

underlayColor

ts
underlayColor?: string;

Background color that will be dimmed when the button is in an active state.

<Badges platforms={['ios']}>

activeOpacity [I]


</Badges>

ts
activeOpacity?: number;

Opacity applied to the underlay when the button is in an active state.

BorderlessButton

This type of button component should be used with simple icon-only or text-only buttons. The interaction will be different depending on platform: on Android a borderless ripple will be rendered (it means that the ripple will animate into a circle that can span outside of the view bounds), whereas on iOS the button will be dimmed (similar to how TouchableOpacity works). In addition to the props of BaseButton, it accepts the following:

<Badges platforms={['ios']}>

activeOpacity [I]


</Badges>

ts
activeOpacity?: number;

Opacity applied to the button when it is in an active state.

Accessibility

To guarantee that buttons are fully accessible, you must wrap your children in a View marked as accessible and include the accessibilityRole="button" prop. This requirement applies to both iOS and Android platforms. Without these adjustments, the button won't be selectable on iOS, and on Android, it won't be clickable in accessibility mode.

tsx
// Not accessible:
const NotAccessibleButton = () => (
<RectButton onPress={this._onPress}>
<Text>Foo</Text>
</RectButton>
);

// Accessible:
const AccessibleButton = () => (
<RectButton onPress={this._onPress}>
<View accessible accessibilityRole="button">
<Text>Bar</Text>
</View>
</RectButton>
);

Design patterns

Components listed here were not designed to behave and look in the same way on both platforms, but rather to be used for handling similar behaviour on iOS and Android taking into consideration their design concepts.

If you wish to get specific information about platforms design patterns, visit official Apple docs and Material.io guideline, which widely describe how to implement coherent design.

This library allows the use of native components with native feedback in adequate situations.

If you do not wish to implement a custom design approach, RectButton and BorderlessButton seem to be absolutely enough and there's no need to use anything else. In all the remaining cases, you can always rely on BaseButton which is a superclass for the other button classes and can be used as a generic React Native Touchable replacement that can be customized to your needs.

Below we list some of the common usecases for button components to be used along with the type of button that should be used according to the platform specific design guidelines.

Lists and action buttons

If you have a list with clickable items or have an action button that needs to display as a separate UI block (vs being inlined in a text) you should use RectButton. It changes opacity on click and additionally supports a ripple effect on Android.

<GifGallery>
<img src={useBaseUrl('gifs/androidsettings.gif')} width="280" />
<img src={useBaseUrl('gifs/iossettings.gif')} width="280" />
</GifGallery>

To determine the emphasis of a button, it's vital to use a fill color or leave it transparent, especially on Android.
For medium emphasis, you may consider outlined buttons which are used for lower impact than fill buttons.

<GifGallery>
<img src={useBaseUrl('gifs/androidbutton.gif')} width="280" />
</GifGallery>

Icon or text only buttons

Use BorderlessButton for simple icon-only or text-only buttons. The interaction will be different depending on platform: on Android a borderless ripple will be rendered, whereas on iOS the button will be dimmed.
It should be used if you wish to handle non-crucial actions and supportive behaviour.

<GifGallery>
<img src={useBaseUrl('gifs/androidmail.gif')} width="280" />
<img src={useBaseUrl('gifs/iosmail.gif')} width="280" />
</GifGallery>

---

Packages/Docs Gesture Handler/Docs/Components/Pressable (packages/docs-gesture-handler/docs/components/pressable.mdx)

---
id: pressable
title: Pressable
sidebar_label: Pressable
sidebar_position: 2
---

import useBaseUrl from '@docusaurus/useBaseUrl';
import GifGallery from '@site/components/GifGallery';

:::info
This component is a drop-in replacement for the Pressable component.
:::

<GifGallery>
<img src={useBaseUrl('gifs/pressable.gif')} width="70%" />
</GifGallery>

Pressable is a component that can detect various stages of tap, press, and hover interactions on any of its children.

To use Pressable, ensure that your app is wrapped in GestureHandlerRootView and import it as follows:

ts
import { Pressable } from 'react-native-gesture-handler';

Properties

children

ts
children?:
| React.ReactNode
| ((state: PressableStateCallbackType) => React.ReactNode);

Either children or a render prop that receives a boolean reflecting whether the component is currently pressed.

style

ts
style?:
| StyleProp<ViewStyle>
| ((state: PressableStateCallbackType) => StyleProp<ViewStyle>);

Either view styles or a function that receives a boolean reflecting whether the component is currently pressed and returns view styles.

onPress

ts
onPress?: null | ((event: PressableEvent) => void);

Called after onPressOut when a single tap gesture is detected. Details about the event object can be found in the PressableEvent section below.

onPressIn

ts
onPressIn?: null | ((event: PressableEvent) => void);

Called before onPress when a touch is engaged. Details about the event object can be found in the PressableEvent section below.

onPressOut

ts
onPressOut?: null | ((event: PressableEvent) => void);

Called before onPress when a touch is released (before onPress). Details about the event object can be found in the PressableEvent section below.

onLongPress

ts
onLongPress?: null | ((event: PressableEvent) => void);

Called immediately after pointer has been down for at least delayLongPress milliseconds.

After onLongPress has been called, onPressOut will be called as soon as the pointer is lifted and onPress will not be called at all.

cancelable

ts
cancelable?: null | boolean;

Whether a press gesture can be interrupted by a parent gesture such as a scroll event. Defaults to true.

<Badges platforms={['android', 'web']}>

onHoverIn [A][W]


</Badges>

ts
onHoverIn?: null | ((event: PressableEvent) => void);

Called when pointer is hovering over the element.

<Badges platforms={['android', 'web']}>

onHoverOut [A][W]


</Badges>

ts
onHoverOut?: null | ((event: PressableEvent) => void);

Called when pointer stops hovering over the element.

<Badges platforms={['web']}>

delayHoverIn [W]


</Badges>

ts
delayHoverIn?: number | null;

Duration to wait after hover in before calling onHoverIn.

<Badges platforms={['web']}>

delayHoverOut [W]


</Badges>

ts
delayHoverOut?: number | null;

Duration to wait after hover out before calling onHoverOut.

delayLongPress

ts
delayLongPress?: null | number;

Duration (in milliseconds) from onPressIn before onLongPress is called. Default value is 500 ms.

disabled

ts
disabled?: null | boolean;

Whether the Pressable behavior is disabled.

<Badges platforms={['android', 'ios']}>

hitSlop [A][I]


</Badges>

ts
hitSlop?: null | Insets | number;

Additional distance outside of the view in which a press is detected and onPressIn is triggered.

The Insets type is essentially the same as Rect.

<Badges platforms={['android', 'ios']}>

pressRetentionOffset [A][I]


</Badges>

ts
pressRetentionOffset?: null | Insets | number;

Additional distance outside of the view (or hitSlop if present) in which a touch is considered a
press before onPressOut is triggered.

The Insets type is essentially the same as Rect.

<Badges platforms={['android']}>

android_disableSound [A]


</Badges>

ts
android_disableSound?: null | boolean;

If true, doesn't play system sound on touch.

<Badges platforms={['android']}>

android_ripple [A]


</Badges>

ts
android_ripple?: null | PressableAndroidRippleConfig;

Enables the Android ripple effect and configures its color, radius and other parameters.

Accepts values of type RippleConfig.

<Badges platforms={['android']}>

needsOffscreenAlphaCompositing [A]


</Badges>

ts
needsOffscreenAlphaCompositing?: boolean;

Whether the view should render with an offscreen alpha-compositing buffer when its opacity is less than 1. Defaults to false.

testOnly_pressed

ts
testOnly_pressed?: null | boolean;

Used only for documentation or testing (e.g. snapshot testing).

unstable_pressDelay

ts
unstable_pressDelay?: number | undefined;

Duration (in milliseconds) to wait after press down before calling onPressIn.

PressableEvent

All Pressable callbacks receive an event object as a parameter, which is of type PressableEvent and has the following structure:

ts
export type PressableEvent = { nativeEvent: InnerPressableEvent };

export type InnerPressableEvent = {
changedTouches: InnerPressableEvent[];
identifier: number;
locationX: number;
locationY: number;
pageX: number;
pageY: number;
target: number;
timestamp: number;
touches: InnerPressableEvent[];
force?: number;
};

Example

See the full example in Gesture Handler repository.

<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[5, 20]}
src={
import { View, Text, StyleSheet } from 'react-native';
import {
GestureHandlerRootView,
Pressable,
} from 'react-native-gesture-handler';

export default function Example() {
return (
<GestureHandlerRootView>
<Pressable
style={({ pressed }) => (pressed ? styles.highlight : styles.pressable)}
hitSlop={20}
pressRetentionOffset={20}>
<View style={styles.textWrapper}>
<Text style={styles.text}>Pressable!</Text>
</View>
</Pressable>
</GestureHandlerRootView>
);
}

const styles = StyleSheet.create({
pressable: {
width: 120,
height: 120,
backgroundColor: 'mediumpurple',
borderWidth: StyleSheet.hairlineWidth,
},
highlight: {
width: 120,
height: 120,
backgroundColor: 'red',
borderWidth: StyleSheet.hairlineWidth,
},
textWrapper: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
text: {
color: 'black',
},
});
}/>

---

Packages/Docs Gesture Handler/Docs/Components/Reanimated Drawer Layout (packages/docs-gesture-handler/docs/components/reanimated-drawer-layout.mdx)

---
id: reanimated-drawer-layout
title: Reanimated Drawer Layout
sidebar_label: Reanimated Drawer Layout
sidebar_position: 4
---

import useBaseUrl from '@docusaurus/useBaseUrl';

import MouseButtonProp from '../gestures/\_shared/mouse-button.mdx';

:::info
This component acts as a cross-platform replacement for React Native's DrawerLayoutAndroid component, written using Reanimated. For detailed information on standard parameters, please refer to the React Native documentation.
:::

To use ReanimatedDrawerLayout, first ensure that Reanimated is installed and that your app is wrapped in GestureHandlerRootView. You can then import it as follows:

ts
import ReanimatedDrawerLayout from 'react-native-gesture-handler/ReanimatedDrawerLayout';

Properties

drawerType

ts
drawerType?: DrawerType;

Specifies the way the drawer will be displayed.
Accepts values of the DrawerType enum. Defaults to FRONT.

- FRONT The drawer will be displayed above the content view.
- BACK The drawer will be displayed below the content view, revealed by sliding away the content view.
- SLIDE The drawer will appear attached to the content view, opening it slides both the drawer and the content view.

| FRONT | BACK | SLIDE |
| ----------------------------------------------------- | ---------------------------------------------------- | ----------------------------------------------------- |
| <img src={useBaseUrl('gifs/new-drawer-front.gif')} /> | <img src={useBaseUrl('gifs/new-drawer-back.gif')} /> | <img src={useBaseUrl('gifs/new-drawer-slide.gif')} /> |

drawerBackgroundColor

ts
drawerBackgroundColor?: string;

Color of the drawer's background.

drawerWidth

ts
drawerWidth?: number;

Width of the drawer. Defaults to 200.

drawerLockMode

<CollapsibleCode
label="Show composed types definitions"
expandedLabel="Hide composed types definitions"
lineBounds={[0, 1]}
src={
drawerLockMode?: DrawerLockMode;

export enum DrawerLockMode {
UNLOCKED,
LOCKED_CLOSED,
LOCKED_OPEN,
}
}/>

Specifies the lock mode of the drawer.

- UNLOCKED The drawer is unlocked and can be opened or closed by gestures.
- LOCKED_CLOSED The drawer will move freely until it settles in a closed position, then the gestures will be disabled.
- LOCKED_OPEN The drawer will move freely until it settles in an opened position, then the gestures will be disabled.

keyboardDismissMode

<CollapsibleCode
label="Show composed types definitions"
expandedLabel="Hide composed types definitions"
lineBounds={[0, 1]}
src={
keyboardDismissMode?: DrawerKeyboardDismissMode;

export enum DrawerKeyboardDismissMode {
NONE,
ON_DRAG,
}
}/>

Determines if the system keyboard should be closed upon dragging the drawer.

animationSpeed

ts
animationSpeed?: number;

Speed of animation that will play when letting go, or dismissing the drawer.

minSwipeDistance

ts
minSwipeDistance?: number;

Minimal distance to swipe before the drawer starts moving.

rootContainerStyle

ts
rootContainerStyle?: StyleProp<ViewStyle>;

Style applied to the outermost container that wraps both the content view and the drawer. Note that this container has flex: 1 and overflow: 'hidden' applied by default.

contentContainerStyle

ts
contentContainerStyle?: StyleProp<ViewStyle>;

Style applied to the container wrapping the content view (the children) and the background overlay.

drawerContainerStyle

ts
drawerContainerStyle?: StyleProp<ViewStyle>;

Style applied to the container wrapping the drawer (the view returned by renderNavigationView).

edgeWidth

ts
edgeWidth?: number;

Width of the invisible, draggable area on the edge of the content view, which can be dragged to open the drawer.

hideStatusBar

ts
hideStatusBar?: boolean;

When set to true, drawer component will use StatusBar API to hide the OS status bar when the drawer is dragged or idle in the open position.

statusBarAnimation

<CollapsibleCode
label="Show composed types definitions"
expandedLabel="Hide composed types definitions"
lineBounds={[0, 1]}
collapsed={false}
src={
statusBarAnimation?: StatusBarAnimation;

export type StatusBarAnimation = 'none' | 'fade' | 'slide';
}/>

May be used in combination with hideStatusBar to select the animation used for hiding the status bar.
See StatusBar API docs. Defaults to slide.

overlayColor

ts
overlayColor?: string;

Color of the background overlay on top of the content window when the drawer is open.
This color's opacity animates from 0% to 100% as the drawer transitions from closed to open. Defaults to rgba(0, 0, 0, 0.7).

renderNavigationView

ts
renderNavigationView: (
progressAnimatedValue: SharedValue<number>
) => ReactNode;

A renderer function for the drawer component is provided with a progress parameter called progressAnimatedValue, which is a SharedValue indicating the progress of the drawer's opening or closing animation. This value is 0 when the drawer is fully closed and 1 when it is fully opened. The drawer component can use this value to animate its children during the opening or closing process. This function must return a ReactNode.

onDrawerClose

ts
onDrawerClose?: () => void;

A function which is called when the drawer has been closed.

onDrawerOpen

ts
onDrawerOpen?: () => void;

A function which is called when the drawer has been opened.

onDrawerSlide

ts
onDrawerSlide?: (position: number) => void;

A function is called when the drawer is moving or animating, provided with a position parameter. This position value indicates the progress of the drawer's opening or closing animation. It equals 0 when the drawer is closed and 1 when the drawer is fully opened. This value can be utilized by the drawer component to animate its children as the drawer opens or closes.

onDrawerStateChanged

<CollapsibleCode
label="Show composed types definitions"
expandedLabel="Hide composed types definitions"
lineBounds={[0, 4]}
src={
onDrawerStateChanged?: (
newState: DrawerState,
drawerWillShow: boolean
) => void;

export enum DrawerState {
IDLE,
DRAGGING,
SETTLING,
}
}/>

A function is called when the status of the drawer changes, taking newState to represent the drawer's interaction state and drawerWillShow, which is true when the drawer starts animating towards the open position and false otherwise.

<Badges platforms={['ios', 'web']}>

enableTrackpadTwoFingerGesture [I][W]


</Badges>

ts
enableTrackpadTwoFingerGesture?: boolean | SharedValue<boolean>;

Enables two-finger gestures on supported devices, for example iPads with trackpads. If not enabled the gesture will require click + drag, with enableTrackpadTwoFingerGesture swiping with two fingers will also trigger the gesture.


children

ts
children?: ReactNode | ((openValue?: SharedValue<number>) => ReactNode);

Either a component rendered in the content view or a function. If children is a function, it receives an openValue parameter - SharedValue that indicates the progress of the drawer's opening or closing animation. This value equals 0 when the drawer is closed and 1 when it is fully opened. The drawer component can use this value to animate its children during the opening or closing process. This function must return a ReactNode.

<MouseButtonProp />

<Badges platforms={['web']}>

enableContextMenu [W]


</Badges>

ts
enableContextMenu: boolean;

Specifies whether the context menu should be enabled after clicking on the underlying view with the right mouse button. Default value is set to false if MouseButton.RIGHT is specified.

<Badges platforms={['web']}>

userSelect [W]


</Badges>

ts
userSelect: 'none' | 'auto' | 'text';

This parameter allows specifying which userSelect property should be applied to the underlying view. Default value is set to "none".

<Badges platforms={['web']}>

activeCursor [W]


</Badges>

ts
activeCursor?: ActiveCursor | SharedValue<ActiveCursor>;

This parameter allows specifying which cursor should be used when the gesture activates. Supports all CSS cursor values (e.g. "grab", "zoom-in"). Default value is set to "auto".

Drawer ref methods

Using a reference to ReanimatedDrawerLayout allows you to manually trigger the opening and closing of the component.

ts
const drawerRef = useRef<DrawerLayoutMethods>(null);

Both methods accept an optional options parameter, which allows you to customize the animation of the drawer movement.

ts
export type DrawerMovementOption = {
initialVelocity?: number;
animationSpeed?: number;
};

openDrawer

ts
openDrawer: (options?: DrawerMovementOption) => void;

Allows manually opening the drawer.

closeDrawer

ts
closeDrawer: (options?: DrawerMovementOption) => void;

Allows manually closing the drawer.

Example

Example of a ReanimatedDrawerLayout component can be found in Gesture Handler repository.


<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[14, 49]}
src={
import React, { useRef } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import {
GestureDetector,
GestureHandlerRootView,
useTapGesture,
} from 'react-native-gesture-handler';

import ReanimatedDrawerLayout, {
DrawerType,
DrawerPosition,
DrawerLayoutMethods,
} from 'react-native-gesture-handler/ReanimatedDrawerLayout';

const DrawerPage = () => {
return (
<View style={styles.drawerContainer}>
<Text>Lorem ipsum</Text>
</View>
);
};

export default function ReanimatedDrawerExample() {
const drawerRef = useRef<DrawerLayoutMethods>(null);
const tapGesture = useTapGesture({
onDeactivate: () => {
drawerRef.current?.openDrawer();
},
runOnJS: true,
});

return (
<GestureHandlerRootView>
<ReanimatedDrawerLayout
ref={drawerRef}
renderNavigationView={() => <DrawerPage />}
drawerPosition={DrawerPosition.LEFT}
drawerType={DrawerType.FRONT}>
<View style={styles.innerContainer}>
<GestureDetector gesture={tapGesture}>
<View style={styles.box}>
<Text>Open drawer</Text>
</View>
</GestureDetector>
</View>
</ReanimatedDrawerLayout>
</GestureHandlerRootView>
);
}

const styles = StyleSheet.create({
drawerContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'pink',
},
innerContainer: {
flex: 1,
backgroundColor: 'white',
alignItems: 'center',
justifyContent: 'center',
gap: 20,
},
box: {
padding: 20,
backgroundColor: 'pink',
},
});
}/>

---

Packages/Docs Gesture Handler/Docs/Components/Reanimated Swipeable (packages/docs-gesture-handler/docs/components/reanimated_swipeable.mdx)

---
id: reanimated_swipeable
title: Reanimated Swipeable
sidebar_label: Reanimated Swipeable
sidebar_position: 3
---

import useBaseUrl from '@docusaurus/useBaseUrl';
import GifGallery from '@site/components/GifGallery'

:::info
This component is a drop-in replacement for the Swipeable component, rewritten using Reanimated.
:::

<GifGallery>
<img src={useBaseUrl("gifs/sampleswipeable.gif")} height="120" />
</GifGallery>


ReanimatedSwipeable is designed for implementing swipeable rows or similar interactions. It places its children inside a pannable container that enables horizontal swiping to the left and right. Depending on the direction of the swipe, one of two "action" containers will be displayed, which can be configured using the renderLeftActions or renderRightActions props.

To use ReanimatedSwipeable, first ensure that Reanimated is installed and that your app is wrapped in GestureHandlerRootView. You can then import it as follows:

ts
import Swipeable from 'react-native-gesture-handler/ReanimatedSwipeable';

Properties

friction

ts
friction?: number;

Number that specifies how much the visual interaction will be delayed compared to the gesture distance.

e.g. value of 1 will indicate that the swipeable panel should exactly follow the gesture, 2 means it is going to be two times "slower".

leftThreshold

ts
leftThreshold?: number;

Distance from the left edge at which released panel will animate to the open state (or the open panel will animate into the closed state). By default it's a half of the panel's width.

rightThreshold

ts
rightThreshold?: number;

Distance from the right edge at which released panel will animate to the open state (or the open panel will animate into the closed state). By default it's a half of the panel's width.

dragOffsetFromLeft

ts
dragOffsetFromLeft?: number | SharedValue<number>;

The horizontal offset from the starting point required to trigger a right-swipe gesture. Defaults to 10.

dragOffsetFromRight

ts
dragOffsetFromRight?: number | SharedValue<number>;

The horizontal offset from the starting point required to trigger a left-swipe gesture. Defaults to -10.

overshootLeft

ts
overshootLeft?: boolean;

A boolean value indicating if the swipeable panel can be pulled further than the left actions panel's width. It is set to true by default as long as the left panel render function is present.

overshootRight

ts
overshootRight?: boolean;

A boolean value indicating if the swipeable panel can be pulled further than the right actions panel's width. It is set to true by default as long as the right panel render function is present.

overshootFriction

ts
overshootFriction?: number;

A number specifying the delay of visual interaction compared to the gesture distance when overshooting. The default value is 1, which means no friction. For a more native feel, try using a value of 8 or higher.

onSwipeableOpen

ts
onSwipeableOpen?: (
direction: SwipeDirection.LEFT | SwipeDirection.RIGHT
) => void;

A function that is called when Swipeable is opened (either right or left).
Receives swipe direction as an argument.

onSwipeableClose

ts
onSwipeableClose?: (
direction: SwipeDirection.LEFT | SwipeDirection.RIGHT
) => void;

A function that is called when Swipeable is closed.
Receives swipe direction as an argument.

onSwipeableWillOpen

ts
onSwipeableWillOpen?: (
direction: SwipeDirection.LEFT | SwipeDirection.RIGHT
) => void;

A function that is called when Swipeable starts animating on open (either right or left).
Receives swipe direction as an argument.

onSwipeableWillClose

ts
onSwipeableWillClose?: (
direction: SwipeDirection.LEFT | SwipeDirection.RIGHT
) => void;

A function that is called when Swipeable starts animating on close.
Receives swipe direction as an argument.

onSwipeableOpenStartDrag

ts
onSwipeableOpenStartDrag?: (
direction: SwipeDirection.LEFT | SwipeDirection.RIGHT
) => void;

A function that is called when a user starts to drag the Swipeable to open.
Receives swipe direction as an argument.

onSwipeableCloseStartDrag

ts
onSwipeableCloseStartDrag?: (
direction: SwipeDirection.LEFT | SwipeDirection.RIGHT
) => void;

A function that is called when a user starts to drag the Swipeable to close.
Receives swipe direction as an argument.

renderLeftActions

ts
renderLeftActions?: (
progress: SharedValue<number>,
translation: SharedValue<number>,
swipeableMethods: SwipeableMethods
) => React.ReactNode;

A function that returns a component which will be rendered beneath the Swipeable after it is swiped to the right. This function accepts the following parameters:

- progress - a SharedValue that represents the swiping progress relative to the width of the returned element.
- It equals 0 when the Swipeable is fully closed and 1 when it is fully opened.
- As the element overshoots its open position, the value approaches Infinity.
- translation - a horizontal offset of the Swipeable relative to its closed position.
- swipeableMethods - provides an object exposing methods detailed in the methods section.

This function must return a ReactNode. To accommodate rtl (right-to-left) flexbox layouts, use the flexDirection style property.

renderRightActions

ts
renderRightActions?: (
progress: SharedValue<number>,
translation: SharedValue<number>,
swipeableMethods: SwipeableMethods
) => React.ReactNode;

A function that returns a component which will be rendered beneath the Swipeable after it is swiped to the left. This function accepts the following parameters:

- progress - a SharedValue that represents the swiping progress relative to the width of the returned element.
- It equals 0 when the Swipeable is fully closed and 1 when it is fully opened.
- As the element overshoots its open position, the value approaches Infinity.
- translation - a horizontal offset of the Swipeable relative to its closed position.
- swipeableMethods - provides an object exposing methods detailed in the methods section.

This function must return a ReactNode. To accommodate rtl (right-to-left) flexbox layouts, use the flexDirection style property.


containerStyle

ts
containerStyle?: StyleProp<ViewStyle>;

Style object for the container (Animated.View).

childrenContainerStyle

ts
childrenContainerStyle?: StyleProp<ViewStyle>;

Style object for the children container (Animated.View).

simultaneousWith

ts
simultaneousWith?: AnyGesture | AnyGesture[];

Gestures to be recognized simultaneously with the Swipeable (see simultaneousWith).

requireToFail

ts
requireToFail?: AnyGesture | AnyGesture[];

Gestures that Swipeable has to wait for before activating (see requireToFail).


block

ts
block?: AnyGesture | AnyGesture[];

Gestures that Swipeable will prevent from activating (see block).

<Badges platforms={['ios', 'web']}>

enableTrackpadTwoFingerGesture [I][W]


</Badges>

ts
enableTrackpadTwoFingerGesture?: boolean | SharedValue<boolean>;

Enables two-finger gestures on supported devices, for example iPads with trackpads. If not enabled the gesture will require click + drag, with enableTrackpadTwoFingerGesture swiping with two fingers will also trigger the gesture.

enabled

ts
enabled?: boolean | SharedValue<boolean>;

Indicates whether ReanimatedSwipeable should be analyzing the stream of touch events or not. Defaults to true.

testID

ts
testID?: string;

Sets a testID property, allowing for querying ReanimatedSwipeable for it in tests.

hitSlop

<CollapsibleCode
label="Show composed types definitions"
expandedLabel="Hide composed types definitions"
lineBounds={[0, 1]}
collapsed={false}
src={
hitSlop?: HitSlop | SharedValue<HitSlop>;

type HitSlop =
| number
| null
| undefined
| Partial<
Record<
'left' | 'right' | 'top' | 'bottom' | 'vertical' | 'horizontal',
number
>
>
| Record<'width' | 'left', number>
| Record<'width' | 'right', number>
| Record<'height' | 'top', number>
| Record<'height' | 'bottom', number>;
}/>

This parameter enables control over what part of the connected view area can be used to begin recognizing the gesture.
When a negative number is provided, the bounds of the view will reduce the area by the given number of points in each of the sides evenly.
See hitSlop section in Pan gesture for more details.

Swipeable ref methods

Using a reference to Swipeable allows you to manually trigger the opening and closing of the component, as well as reset its swiping state.

ts
const swipeableRef = useRef<SwipeableMethods>(null);

close

ts
close: () => void;

A method that closes component.

openLeft

ts
openLeft: () => void;

A method that opens component on left side.

openRight

ts
openRight: () => void;

A method that opens component on right side.

reset

ts
reset: () => void;

A method that resets the swiping states of this Swipeable component. Unlike close, this method does not trigger any animation.

Example

Example of a Swipeable component can be found in Gesture Handler repository.

<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[10, 41]}
src={
import React from 'react';
import { Text, StyleSheet } from 'react-native';

import { GestureHandlerRootView } from 'react-native-gesture-handler';
import ReanimatedSwipeable from 'react-native-gesture-handler/ReanimatedSwipeable';
import Reanimated, {
SharedValue,
useAnimatedStyle,
} from 'react-native-reanimated';

function RightAction(prog: SharedValue<number>, drag: SharedValue<number>) {
const styleAnimation = useAnimatedStyle(() => {
console.log('showRightProgress:', prog.value);
console.log('appliedTranslation:', drag.value);

return {
transform: [{ translateX: drag.value + 50 }],
};
});

return (
<Reanimated.View style={styleAnimation}>
<Text style={styles.rightAction}>Text</Text>
</Reanimated.View>
);
}

export default function Example() {
return (
<GestureHandlerRootView>
<ReanimatedSwipeable
containerStyle={styles.swipeable}
friction={2}
enableTrackpadTwoFingerGesture
rightThreshold={40}
renderRightActions={RightAction}>
<Text>Swipe me!</Text>
</ReanimatedSwipeable>
</GestureHandlerRootView>
);
}

const styles = StyleSheet.create({
rightAction: { width: 50, height: 50, backgroundColor: 'purple' },
separator: {
width: '100%',
borderTopWidth: 1,
},
swipeable: {
height: 50,
backgroundColor: 'papayawhip',
alignItems: 'center',
},
});
}/>

---

Packages/Docs Gesture Handler/Docs/Components/Touchable (packages/docs-gesture-handler/docs/components/touchable.mdx)

---
id: touchable
title: Touchable
sidebar_label: Touchable
sidebar_position: 1
---

:::note
This section refers to new Touchable component, meant to replace both buttons and touchables. If you are looking for documentation for the deprecated touchable components, check out the Legacy Touchables section.
:::

Touchable is a versatile new component introduced in Gesture Handler 3 to supersede previous button implementations. Designed for maximum flexibility, it provides a highly customizable interface for native touch handling while ensuring consistent behavior across platforms.

Touchable provides a simple interface for the common animations like opacity, underlay, and scale, implemented entirely on the platform. On Android, it also exposes the native ripple effect on press (turned off by default).

If the provided animations are not sufficient, it's possible to use Touchable to create fully custom interactions using either Reanimated or Animated API.

Replacing old buttons

If you were using RectButton or BorderlessButton in your app, you should replace them with Touchable. Check out the full code in the example section below.

RectButton

To replace RectButton with Touchable, add underlayColor="black" to your Touchable. This will tint the underlay when the button is pressed. The legacy RectButton switches states instantly with no fade, so also set animationDuration={0} to match its behavior.

tsx
<Touchable
...
underlayColor="black"
animationDuration={0}/>

:::note Android ripple
The legacy RectButton shows the native theme ripple on Android by default, while Touchable disables the ripple unless androidRipple is set. To keep the legacy Android feedback, set androidRipple={{}} on Android instead of underlayColor/animationDuration — combining the two would render a ripple and an underlay animation simultaneously. Use Platform.select to apply the right props per platform:

tsx
import { Platform } from 'react-native';

<Touchable
{...Platform.select({
android: { androidRipple: {} },
default: { underlayColor: 'black', animationDuration: 0 },
})}
/>


:::

BorderlessButton

Replacing BorderlessButton with Touchable is as easy as replacing RectButton. Add activeOpacity={0.3} to dim the whole component on press, and animationDuration={0} to keep the transition instant — matching the legacy BorderlessButton.

tsx
<Touchable
...
activeOpacity={0.3}
animationDuration={0}/>

:::note Android ripple
Same caveat as RectButton: the legacy BorderlessButton shows the native theme ripple on Android. To preserve that, set androidRipple={{ borderless: true }} on Android instead of activeOpacity/animationDuration:

tsx
<Touchable
{...Platform.select({
android: { androidRipple: { borderless: true } },
default: { activeOpacity: 0.3, animationDuration: 0 },
})}
/>

:::

Migrating from legacy Touchable variants

If you were using the specialized touchable components (TouchableOpacity, TouchableHighlight, TouchableWithoutFeedback, or TouchableNativeFeedback), you can replicate their behavior with the unified Touchable component.

TouchableOpacity

To replace TouchableOpacity, add activeOpacity={0.2}. The legacy TouchableOpacity dims instantly on press-in and fades back over 150ms on release, so set animationDuration={{ in: 0, out: 150 }} to mirror that timing.

tsx
<Touchable
...
activeOpacity={0.2}
animationDuration={{ in: 0, out: 150 }}/>

TouchableHighlight

A perfect 1:1 replacement isn't possible — in TouchableHighlight the container's own background becomes the underlay (solid underlayColor) and activeOpacity dims just the children on top, so the underlay shows through the dimmed children. Touchable instead has a separate underlay layer between the background and children, and its activeOpacity dims the whole component (background + underlay + children together). The closest approximation: carry underlayColor and activeOpacity over unchanged and add activeUnderlayOpacity={1} so the underlay layer is rendered solid.

tsx
<Touchable
...
underlayColor="#DDDDDD"
activeUnderlayOpacity={1}
activeOpacity={0.6}/>

TouchableWithoutFeedback

To replace TouchableWithoutFeedback, use a plain Touchable.

tsx
<Touchable ... />

TouchableNativeFeedback

To replicate TouchableNativeFeedback behavior, use the androidRipple prop. The legacy component defaults to useForeground: true, so set foreground: true to match — drop it only if the original code passed useForeground={false}. Add color, radius, or borderless if the original code customized the background prop.

tsx
<Touchable
...
androidRipple={{ foreground: true }}/>

Example

In this example we will demonstrate how to recreate RectButton and BorderlessButton effects using the Touchable component.

<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[7, 40]}
src={
import React from 'react';
import { StyleSheet, Text } from 'react-native';
import {
GestureHandlerRootView,
Touchable,
} from 'react-native-gesture-handler';

export default function TouchableExample() {
return (
<GestureHandlerRootView style={styles.container}>
<Touchable
onPress={() => {
console.log('BaseButton built with Touchable');
}}
style={[styles.button, { backgroundColor: '#7d63d9' }]}>
<Text style={styles.buttonText}>BaseButton</Text>
</Touchable>

<Touchable
onPress={() => {
console.log('RectButton built with Touchable');
}}
style={[styles.button, { backgroundColor: '#4f9a84' }]}
underlayColor="black"
animationDuration={0}>
<Text style={styles.buttonText}>RectButton</Text>
</Touchable>

<Touchable
onPress={() => {
console.log('BorderlessButton built with Touchable');
}}
style={[styles.button, { backgroundColor: '#5f97c8' }]}
activeOpacity={0.3}
animationDuration={0}>
<Text style={styles.buttonText}>BorderlessButton</Text>
</Touchable>
</GestureHandlerRootView>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',

gap: 20,
},
button: {
width: 200,
height: 70,
borderRadius: 15,
alignItems: 'center',
justifyContent: 'center',
},
buttonText: {
color: 'white',
fontSize: 14,
fontWeight: '600',
},
});
}/>


Properties

activeOpacity

ts
activeOpacity?: number;

Defines the opacity of the whole component when the button is active.

defaultOpacity

ts
defaultOpacity?: number;

Defines the opacity of the whole component when the button is active. By default set to 1.

activeScale

ts
activeScale?: number;

Defines the scale of the whole component when the button is active.

defaultScale

ts
defaultScale?: number;

Defines the scale of the whole component when the button is inactive. By default set to 1.

activeUnderlayOpacity

ts
activeUnderlayOpacity?: number;

Defines the opacity of the underlay when the button is active. By default set to 0.105.

defaultUnderlayOpacity

ts
defaultUnderlayOpacity?: number;

Defines the initial opacity of underlay when the button is inactive. By default set to 0.

hoverOpacity

ts
hoverOpacity?: number;

Defines the opacity of the whole component when the button is hovered. By default falls back to defaultOpacity.

hoverScale

ts
hoverScale?: number;

Defines the scale of the whole component when the button is hovered. By default falls back to defaultScale.

hoverUnderlayOpacity

ts
hoverUnderlayOpacity?: number;

Defines the opacity of the underlay when the button is hovered. By default falls back to defaultUnderlayOpacity.

animationDuration

<CollapsibleCode
label="Show composed types definitions"
expandedLabel="Hide composed types definitions"
lineBounds={[0, 1]}
src={
animationDuration?: AnimationDuration;

type InOutDuration = { in: number; out: number };

type AnimationDuration =
| number
| (InOutDuration & {
tap?: Partial<InOutDuration>;
hover?: Partial<InOutDuration>;
longPress?: { out: number };
})
| {
tap: InOutDuration;
hover: InOutDuration;
longPress?: { out: number };
};
}/>

Press and hover animation timing, in milliseconds. Defaults to 50ms for the in phase and 100ms for the out phase.

Each animation has two phases — in (running while the pointer engages the component) and out (running after the pointer releases) — across two categories:
- tap — applies to presses.
- hover — pointer hover.

longPress is an optional override for the press-out timing once the press has been held past delayLongPress. It only has an out field (the press-in is always the tap in duration). If omitted, the long-press release uses the resolved tap out duration.

Three input shapes are accepted:

1. A single number applied to every phase of every category:

tsx
<Touchable animationDuration={200} />

2. A baseline in / out with optional per-category overrides — categories that aren't specified inherit the baseline, and within a category any field left out also inherits from the baseline:

tsx
<Touchable
animationDuration={{
in: 50,
out: 200,
hover: { in: 400 },
}}
/>

3. Both categories specified explicitly (no baseline) — every field must be supplied:

tsx
<Touchable
animationDuration={{
tap: { in: 0, out: 200 },
hover: { in: 300, out: 300 },
}}
/>

To give a long press its own release timing, add longPress.out. The switch fires when the press is held past delayLongPress:

tsx
<Touchable
delayLongPress={400}
onLongPress={() => {}}
animationDuration={{
in: 50,
out: 100,
longPress: { out: 500 },
}}
/>

underlayColor

ts
underlayColor?: string;

Background color of the underlay. This only takes effect when activeUnderlayOpacity or defaultUnderlayOpacity is set. By default set to transparent.

exclusive

ts
exclusive?: boolean;

Defines whether pressing this button prevents other buttons exported by Gesture Handler from being pressed. By default set to true.

<Badges platforms={['android']}>

touchSoundDisabled [A]


</Badges>

ts
touchSoundDisabled?: boolean;

If set to true, the system will not play a sound when the button is pressed.

onPressIn

<CollapsibleCode
label="Show composed types definitions"
expandedLabel="Hide composed types definitions"
lineBounds={[0, 1]}
src={
onPressIn?: (e: ButtonEvent) => void;

type ButtonEvent = {
pointerInside: boolean;
x: number;
y: number;
absoluteX: number;
absoluteY: number;
numberOfPointers: number;
pointerType: PointerType;
}

enum PointerType {
TOUCH,
STYLUS,
MOUSE,
KEY,
OTHER,
}
}/>

Triggered when the button gets pressed (analogous to onPressIn in Pressable from RN core).

onPressOut

<CollapsibleCode
label="Show composed types definitions"
expandedLabel="Hide composed types definitions"
lineBounds={[0, 1]}
src={
onPressOut?: (e: ButtonEvent) => void;

type ButtonEvent = {
pointerInside: boolean;
x: number;
y: number;
absoluteX: number;
absoluteY: number;
numberOfPointers: number;
pointerType: PointerType;
}

enum PointerType {
TOUCH,
STYLUS,
MOUSE,
KEY,
OTHER,
}
}/>

Triggered when the button gets released or the pointer moves outside of the button area (analogous to onPressOut in Pressable from RN core).

onPress

<CollapsibleCode
label="Show composed types definitions"
expandedLabel="Hide composed types definitions"
lineBounds={[0, 1]}
src={
onPress?: (e: ButtonEvent) => void;

type ButtonEvent = {
pointerInside: boolean;
x: number;
y: number;
absoluteX: number;
absoluteY: number;
numberOfPointers: number;
pointerType: PointerType;
}

enum PointerType {
TOUCH,
STYLUS,
MOUSE,
KEY,
OTHER,
}
}/>

Triggered when the button gets pressed (analogous to onPress in Pressable from RN core).

onLongPress

<CollapsibleCode
label="Show composed types definitions"
expandedLabel="Hide composed types definitions"
lineBounds={[0, 1]}
src={
onLongPress?: (e: ButtonEvent) => void;

type ButtonEvent = {
pointerInside: boolean;
x: number;
y: number;
absoluteX: number;
absoluteY: number;
numberOfPointers: number;
pointerType: PointerType;
}

enum PointerType {
TOUCH,
STYLUS,
MOUSE,
KEY,
OTHER,
}
}/>

Triggered when the button gets pressed for at least delayLongPress milliseconds.

onHoverIn

<CollapsibleCode
label="Show composed types definitions"
expandedLabel="Hide composed types definitions"
lineBounds={[0, 1]}
src={
onHoverIn?: (e: ButtonEvent) => void;

type ButtonEvent = {
pointerInside: boolean;
x: number;
y: number;
absoluteX: number;
absoluteY: number;
numberOfPointers: number;
pointerType: PointerType;
}

enum PointerType {
TOUCH,
STYLUS,
MOUSE,
KEY,
OTHER,
}
}/>

Triggered when a non-touch pointer - a mouse, a trackpad cursor, or a hovering stylus - moves over the button (analogous to onHoverIn in Pressable from RN core). Touch pointers never report hover.

onHoverOut

<CollapsibleCode
label="Show composed types definitions"
expandedLabel="Hide composed types definitions"
lineBounds={[0, 1]}
src={
onHoverOut?: (e: ButtonEvent) => void;

type ButtonEvent = {
pointerInside: boolean;
x: number;
y: number;
absoluteX: number;
absoluteY: number;
numberOfPointers: number;
pointerType: PointerType;
}

enum PointerType {
TOUCH,
STYLUS,
MOUSE,
KEY,
OTHER,
}
}/>

Triggered when the hovering pointer leaves the button (analogous to onHoverOut in Pressable from RN core).

delayLongPress

ts
delayLongPress?: number;

Defines the delay, in milliseconds, after which the onLongPress callback gets called. By default set to 600.


<Badges platforms={['android']}>

androidRipple [A]


</Badges>

<CollapsibleCode
label="Show composed types definitions"
expandedLabel="Hide composed types definitions"
lineBounds={[0, 1]}
src={
androidRipple?: PressableAndroidRippleConfig;

type PressableAndroidRippleConfig = {
color?: (string | OpaqueColorValue);
borderless?: boolean;
radius?: number;
foreground?: boolean;
}
}/>

Configuration for the ripple effect on Android. If not provided, the ripple effect will be disabled. If {} is provided, the ripple effect will be enabled with default configuration.

<Badges platforms={['android', 'ios']}>

cancelOnLeave [A][I]


</Badges>

ts
cancelOnLeave?: boolean;

Whether the touch should be canceled when the pointer leaves the component. By default set to true. On web this prop doesn't have any effect and behaves as if true was set.

<Badges platforms={['android']}>

needsOffscreenAlphaCompositing [A]


</Badges>

ts
needsOffscreenAlphaCompositing?: boolean;

Whether the view should render with an offscreen alpha-compositing buffer when its opacity is less than 1. Defaults to false.

---

Packages/Docs Gesture Handler/Docs/Components/Touchables (packages/docs-gesture-handler/docs/components/touchables.md)

---
id: legacy-touchables
title: Legacy Touchables
sidebar_label: Legacy Touchables
sidebar_position: 7
---

:::warning
Touchables will be removed in the future version of Gesture Handler. Use Touchable instead.
:::

Gesture Handler library provides an implementation of RN's touchable components that are based on native buttons and do not rely on the JS responder system utilized by RN. Our touchable implementation follows the same API and aims to be a drop-in replacement for touchables available in React Native.

React Native's touchables API can be found here:

- Touchable Native Feedback
- Touchable Highlight
- Touchable Opacity
- Touchable Without Feedback

All major touchable properties (except for pressRetentionOffset) have been adopted and should behave in a similar way as with RN's touchables.

The motivation for using RNGH touchables as a replacement for these imported from React Native is to follow built-in native behavior more closely by utilizing the platform native touch system instead of relying on the JS responder system.
These touchables and their feedback behavior are deeply integrated with native
gesture ecosystem and could be connected with other native components (e.g. ScrollView) and Gesture Handlers easily and in a more predictable way, which
follows native apps' behavior.

Our intention was to make the switch for these touchables as simple as possible. In order to use RNGH's touchables, the only thing you need to do is to change the library from which you import touchable components.

:::info
Gesture Handler's TouchableOpacity uses native driver for animations by default. If this causes problems for you, you can set useNativeAnimations prop to false.
:::

Example:

ts
import {
TouchableNativeFeedback,
TouchableHighlight,
TouchableOpacity,
TouchableWithoutFeedback,
} from 'react-native';

has to be replaced with:

ts
import {
TouchableNativeFeedback,
TouchableHighlight,
TouchableOpacity,
TouchableWithoutFeedback,
} from 'react-native-gesture-handler';

For a comparison of both touchable implementations see our touchables example

---

Packages/Docs Gesture Handler/Docs/Components/Wrapped Components (packages/docs-gesture-handler/docs/components/wrapped-components.mdx)

---
id: wrapped-components
title: Wrapped Components
sidebar_label: Wrapped Components
sidebar_position: 5
---

Some components come with a Native gesture pre-applied. This allows them to participate in the gesture recognition process. Have a look at the example below.

tsx
import { useState } from 'react';
import { Switch } from 'react-native';
import {
GestureDetector,
GestureHandlerRootView,
useTapGesture,
Switch as RNGHSwitch,
} from 'react-native-gesture-handler';

export default function App() {
const [isEnabled, setIsEnabled] = useState(false);

const tap1 = useTapGesture({
onDeactivate: () => {
console.log('Tapped!');
},
});

const tap2 = useTapGesture({
onDeactivate: () => {
console.log('Tapped!');
},
});

return (
<GestureHandlerRootView style={{ flex: 1, paddingTop: 100 }}>
<GestureDetector gesture={tap1}>
<Switch value={isEnabled} onValueChange={setIsEnabled} />
</GestureDetector>
<GestureDetector gesture={tap2}>
<RNGHSwitch value={isEnabled} onValueChange={setIsEnabled} />
</GestureDetector>
</GestureHandlerRootView>
);
}

On Android, in this scenario, the Switch from React Native cannot be toggled on because the tap1 gesture intercepts it. However, using RNGHSwitch makes it capable of participating in the gesture recognition process. This setup allows the switch to be toggled on while still enabling tap2 to recognize taps on it.

List of wrapped components

Components listed below come with a pre-applied Native gesture.

- FlatList
- ScrollView
- RefreshControl
- TextInput
- Switch

onGestureUpdate_CAN_CAUSE_INFINITE_RERENDER

:::danger
This callback may lead to infinite re-renders if not used carefully.

tsx
export default function App() {
const [gesture, setGesture] = useState<NativeGesture | null>(null);

const updateGesture = (g: NativeGesture) => {
// ❌ Wrong usage: calling setState here triggers a re-render,
// which re-creates the ScrollView's Native gesture, which fires
// this callback again → infinite re-render loop.
setGesture(g);
};

return (
<GestureHandlerRootView style={{ flex: 1 }}>
<ScrollView onGestureUpdate_CAN_CAUSE_INFINITE_RERENDER={updateGesture} />
</GestureHandlerRootView>
);
}


:::

Those components also receive an additional prop named onGestureUpdate_CAN_CAUSE_INFINITE_RERENDER.

ts
onGestureUpdate_CAN_CAUSE_INFINITE_RERENDER?: (gesture: NativeGesture) => void;

This callback is invoked when the wrapped component's underlying Native gesture instance or configuration changes, providing access to the underlying gesture. This can be helpful when setting up relations with other gestures. You can check example usage in our ScrollView component.

---

Packages/Docs Gesture Handler/Docs/Composition/ Category .Json (packages/docs-gesture-handler/docs/composition/_category_.json)

{
"label": "Gesture composition & interactions",
"position": 5,
"link": {
"type": "generated-index"
}
}

---

Packages/Docs Gesture Handler/Docs/Composition/Block (packages/docs-gesture-handler/docs/composition/block.mdx)

---
id: block
title: block
sidebar_label: block
sidebar_position: 5
---

import Block from '!!raw-loader!.//\_examples/props/Block';

block works similarly to requireToFail but the direction of the relation is reversed - instead of being a one-to-many relation, it's many-to-one. It's especially useful for making lists where the ScrollView component needs to wait for every gesture underneath it. All that's required to do is to pass a ref, for example:

<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[22, 86]}
src={Block}/>

---

Packages/Docs Gesture Handler/Docs/Composition/Overview (packages/docs-gesture-handler/docs/composition/overview.mdx)

---
id: overview
title: Gesture composition & interactions
sidebar_label: Overview
sidebar_position: 0
---

Gesture Handler simplifies gesture interactions through dedicated composition hooks and relation properties. To choose the right approach, simply ask: are all the gestures attached to the same component?

- If yes — use composition hooks. These allow you to bundle multiple gestures — including previously composed ones — into a single object for a GestureDetector.
- If no — use relation properties to manually define how gestures interact. Since these properties also support composed gestures, you can mix both methods for more complex layouts.

Composition hooks

- useCompetingGestures — only one of the provided gestures can become active at the same time. The first gesture to activate cancels the rest.
- useSimultaneousGestures — all of the provided gestures can activate at the same time. Activation of one does not cancel the others.
- useExclusiveGestures — only one of the provided gestures can become active, with priority determined by the order of the arguments. A gesture activates only after all higher-priority gestures have failed.

Cross-component interactions

- simultaneousWith — allows gestures attached to different components to be recognized simultaneously.
- requireToFail — delays activation of a gesture until all gestures passed as arguments fail (or don't begin at all).
- block — works like requireToFail with the direction of the relation reversed. Especially useful for lists, where a ScrollView needs to wait for every gesture underneath it.

---

Packages/Docs Gesture Handler/Docs/Composition/Require To Fail (packages/docs-gesture-handler/docs/composition/require-to-fail.mdx)

---
id: require-to-fail
title: requireToFail
sidebar_label: requireToFail
sidebar_position: 4
---

import RequireToFail from '!!raw-loader!.//\_examples/props/RequireToFail';

requireToFail allows delaying activation of the handler until all handlers passed as arguments to this method fail (or don't begin at all).

For example, you may want to have two nested components, both of them can be tapped by the user to trigger different actions: outer view requires one tap, but the inner one requires 2 taps. If you don't want the first tap on the inner view to activate the outer handler, you must make the outer gesture wait until the inner one fails:

<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[8, 38]}
src={RequireToFail}/>

---

Packages/Docs Gesture Handler/Docs/Composition/SimultaneousWith (packages/docs-gesture-handler/docs/composition/simultaneousWith.mdx)

---
id: simultaneous-with
title: simultaneousWith
sidebar_label: simultaneousWith
sidebar_position: 6
---

import SimultaneousWith from '!!raw-loader!.//\_examples/props/SimultaneousWith';

simultaneousWith allows gestures across different components to be recognized simultaneously. For example, you may want to have two nested views, both with tap gesture attached. Both of them require one tap, but tapping the inner one should also activate the gesture attached to the outer view:

<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[8, 37]}
src={SimultaneousWith}/>

---

Packages/Docs Gesture Handler/Docs/Composition/Use Competing Gestures (packages/docs-gesture-handler/docs/composition/use-competing-gestures.mdx)

---
id: use-competing-gestures
title: useCompetingGestures
sidebar_label: useCompetingGestures
sidebar_position: 1
---

import Competing from '!!raw-loader!.//\_examples/hooks/Competing';

Only one of the provided gestures can become active at the same time. The first gesture to become active will cancel the rest of the gestures. It accepts a variable number of arguments.

For example, let's say that you have a component that you want to make draggable but you also want to show additional options on long press. Presumably you would not want the component to move after the long press activates. You can accomplish this using useCompetingGestures:

<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[9, 32]}
src={Competing}/>

---

Packages/Docs Gesture Handler/Docs/Composition/Use Exclusive Gestures (packages/docs-gesture-handler/docs/composition/use-exclusive-gestures.mdx)

---
id: use-exclusive-gestures
title: useExclusiveGestures
sidebar_label: useExclusiveGestures
sidebar_position: 3
---

import Exclusive from '!!raw-loader!.//\_examples/hooks/Exclusive';

Only one of the provided gestures can become active. Priority is determined by the order of the arguments, where the first gesture has the highest priority, and the last has the lowest. A gesture can activate only after all higher-priority gestures before it have failed.

For example, if you want to make a component that responds to single tap as well as to a double tap, you can accomplish that using useExclusiveGestures:

<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[8, 35]}
src={Exclusive}/>

---

Packages/Docs Gesture Handler/Docs/Composition/Use Simultaneous Gestures (packages/docs-gesture-handler/docs/composition/use-simultaneous-gestures.mdx)

---
id: use-simultaneous-gestures
title: useSimultaneousGestures
sidebar_label: useSimultaneousGestures
sidebar_position: 2
---

import Simultaneous from '!!raw-loader!.//\_examples/hooks/Simultaneous';

All of the provided gestures can activate at the same time. Activation of one will not cancel the other.

For example, if you want to make a gallery app, you might want the user to be able to zoom, rotate and pan around photos. You can do it with useSimultaneousGestures:

Note: the useSharedValue and useAnimatedStyle are part of react-native-reanimated.

<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[14, 81]}
src={Simultaneous}/>

---

Packages/Docs Gesture Handler/Docs/Core Components/ Category .Json (packages/docs-gesture-handler/docs/core-components/_category_.json)

{
"label": "Core Components",
"position": 3,
"link": {
"type": "generated-index"
}
}

---

Packages/Docs Gesture Handler/Docs/Core Components/Gesture Detector (packages/docs-gesture-handler/docs/core-components/gesture-detector.mdx)

---
id: gesture-detectors
title: Gesture Detectors
sidebar_label: Gesture detectors
sidebar_position: 2
---

Gesture Detector

The GestureDetector is a key component of react-native-gesture-handler. It supports gestures created either using the hook-based API or the builder pattern. Additionally, it allows for the recognition of multiple gestures through gesture composition. GestureDetector interacts closely with Reanimated. For more details, refer to the Integration with Reanimated section.

When using hook API, you can also integrate it directly with the Animated API. More on that can be found in Integration with Animated section.

:::danger

#### Nesting Gesture Detectors

Because GestureDetector supports both the hook API and the builder pattern, it is important to avoid nesting detectors that use different APIs, as this can result in undefined behavior.

#### Reusing Gestures
Using the same instance of a gesture across multiple Gesture Detectors may result in undefined behavior.
:::

js
import { GestureDetector, useTapGesture } from 'react-native-gesture-handler';

export default function App() {
const tap = useTapGesture({
onDeactivate: () => {
console.log('Tap!');
},
});

return (
<GestureHandlerRootView>
// highlight-next-line
<GestureDetector gesture={tap}>
<Animated.View />
// highlight-next-line
</GestureDetector>
</GestureHandlerRootView>
);
}

Virtual Detectors

Since RNGH3, GestureDetector is a standalone host component. Depending on the view hierarchy, this can occasionally disrupt interactions between specific components. To resolve this, use InterceptingGestureDetector in combination with VirtualGestureDetector.

InterceptingGestureDetector

InterceptingGestureDetector functions similarly to a GestureDetector, but it can also act as a proxy for VirtualGestureDetector within its component subtree. Because it can be used solely to establish the context for virtual detectors, the gesture property is optional.

VirtualGestureDetector

VirtualGestureDetector is similar to the GestureDetector from RNGH2. Because it is not a host component, it does not interfere with the host view hierarchy. This allows you to attach gestures without disrupting functionality that depends on it.

Known use cases

Here are some of the most common use cases for virtual gesture detectors.

#### SVG

You can combine VirtualGestureDetector with react-native-svg to add gesture handling to individual SVG elements.

<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[10, 39]}
src={
import React from 'react';
import { StyleSheet } from 'react-native';
import {
GestureHandlerRootView,
InterceptingGestureDetector,
useTapGesture,
VirtualGestureDetector,
} from 'react-native-gesture-handler';
import Svg, { Circle } from 'react-native-svg';

export default function App() {
const outerTap = useTapGesture({
onDeactivate: () => {
console.log('Box tapped!');
},
});
const innerTap = useTapGesture({
onDeactivate: () => {
console.log('Circle tapped!');
},
});

return (
<GestureHandlerRootView style={styles.container}>
<InterceptingGestureDetector gesture={outerTap}>
<Svg height="250" width="250" style={{ backgroundColor: '#b58df1' }}>
<VirtualGestureDetector gesture={innerTap}>
<Circle
cx="125"
cy="125"
r="125"
fill="#001A72"
onPress={() => {}}
/>
</VirtualGestureDetector>
</Svg>
</InterceptingGestureDetector>
</GestureHandlerRootView>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
});
}/>


#### Text

You can use VirtualGestureDetector to add gesture handling to specific parts of a Text component.

<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[8, 37]}
src={
import * as React from 'react';
import { StyleSheet, Text } from 'react-native';
import {
GestureHandlerRootView,
InterceptingGestureDetector,
VirtualGestureDetector,
useTapGesture,
} from 'react-native-gesture-handler';

export default function App() {
const outerTap = useTapGesture({
onDeactivate: () => {
console.log('Tapped on text!');
},
});

const nestedTap = useTapGesture({
onDeactivate: () => {
console.log('Tapped on nested part!');
},
});

return (
<GestureHandlerRootView style={styles.container}>
<InterceptingGestureDetector gesture={outerTap}>
<Text style={{ fontSize: 18, textAlign: 'center' }}>
Nested text
<VirtualGestureDetector gesture={nestedTap}>
<Text style={{ fontSize: 24, color: '#001A72' }}>
try tapping on this part.
</Text>
</VirtualGestureDetector>
This part is not special :c
</Text>
</InterceptingGestureDetector>
</GestureHandlerRootView>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'space-around',
},
});
}/>


Properties

gesture

ts
gesture: SingleGesture | ComposedGesture;

A gesture object containing the configuration and callbacks. Can be any of the base gestures or any ComposedGesture.

<Badges platforms={['web']}>

userSelect [W]


</Badges>

ts
userSelect: 'none' | 'auto' | 'text';

This parameter allows specifying which userSelect property should be applied to the underlying view. Default value is set to "none".

<Badges platforms={['web']}>

touchAction [W]


</Badges>

ts
touchAction: TouchAction;

This parameter allows specifying which touchAction property should be applied to the underlying view. Supports all CSS touch-action values. Default value is set to "none".

<Badges platforms={['web']}>

enableContextMenu [W]


</Badges>

ts
enableContextMenu: boolean;

Specifies whether the context menu should be enabled after clicking on the underlying view with the right mouse button. Default value is set to false.

---

Packages/Docs Gesture Handler/Docs/Core Components/Root View (packages/docs-gesture-handler/docs/core-components/root-view.mdx)

---
id: root-view
title: GestureHandlerRootView
sidebar_label: GestureHandlerRootView
sidebar_position: 1
---

GestureHandlerRootView is a key component that enables Gesture Handler to intercept touch events, allowing for the implementation of gestures in your app. It should wrap your app's main component, and any component that relies on Gesture Handler's gestures has to be a descendant of this view. For more detailed information, you can check out the under-the-hood section.

tsx
import { GestureHandlerRootView } from 'react-native-gesture-handler';

export default function App() {
return (
<GestureHandlerRootView>
<ActualApp />
</GestureHandlerRootView>
);
}

Keep GestureHandlerRootView as close to the actual root of the app as possible. It's the entry point for all gestures and all gesture relations. The gestures won't be recognized outside of the root view, and relations only work between gestures mounted under the same root view.

:::note
When integrating with navigation libraries, wrapping the navigator component with GestureHandlerRootView is generally sufficient. However, if you encounter issues with gestures not functioning properly, you might need to wrap each individual screen component with GestureHandlerRootView as well.
:::

:::tip
If you're using Gesture Handler in your component library, you may want to wrap your library's code in the GestureHandlerRootView component. This will avoid extra configuration for the user.
:::

Styling

GestureHandlerRootView can be thought of as a regular View component, therefore it accepts all the same props, including style.

If you don't provide anything to the style prop, it will default to { flex: 1 }. If you want to customize the styling of the root view, don't forget to also include flex: 1 in the custom style, otherwise your app won't render anything.

Nesting root views

In case of nested root views, Gesture Handler will only use the top-most one and ignore the nested ones. If you're unsure if one of your dependencies already renders GestureHandlerRootView on its own, don't worry and add one at the root anyway.

unstable_forceActive

ts
unstable_forceActive?: boolean;

If you're having trouble with gestures not working when inside a component provided by a third-party library, even though you've wrapped the entry point with <GestureHandlerRootView>, you can try adding another <GestureHandlerRootView unstable_forceActive> closer to the place the gestures are defined. This way, you can prevent Android from canceling relevant gestures when one of the native views tries to grab lock for delivering touch events.

---

Packages/Docs Gesture Handler/Docs/Fundamentals/ Category .Json (packages/docs-gesture-handler/docs/fundamentals/_category_.json)

{
"label": "Fundamentals",
"position": 1,
"link": {
"type": "generated-index"
}
}

---

Packages/Docs Gesture Handler/Docs/Fundamentals/Animated Interactions (packages/docs-gesture-handler/docs/fundamentals/animated-interactions.mdx)

---
id: animated-interactions
title: Integration with Animated
sidebar_label: Integration with Animated
sidebar_position: 5
---

Using hook API allows for smooth integration with the Animated API by allowing for passing an Animated.event as the argument to the onUpdate callback. The event mapping of Animated.event depends on the useNativeDriver property.

When using Animated API, remember to set useAnimated property to true.

:::danger Mixing Reanimated and Animated
It is not possible to mix Reanimated and Animated within any of the gesture detectors.
:::

<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[8, 33]}
src={
import * as React from 'react';
import { Animated, StyleSheet, useAnimatedValue } from 'react-native';
import {
GestureHandlerRootView,
GestureDetector,
usePanGesture,
} from 'react-native-gesture-handler';

export default function App() {
const value = useAnimatedValue(0);

const event = Animated.event(
[{ nativeEvent: { handlerData: { translationX: value } } }],
{
useNativeDriver: true,
}
);

const gesture = usePanGesture({
// highlight-next-line
onUpdate: event,
// highlight-next-line
useAnimated: true,
});

return (
<GestureHandlerRootView>
<GestureDetector gesture={gesture}>
<Animated.View
style={[styles.box, { transform: [{ translateX: value }] }]}
/>
</GestureDetector>
</GestureHandlerRootView>
);
}

const styles = StyleSheet.create({
box: {
width: 150,
height: 150,
backgroundColor: '#b58df1',
},
});
}/>

useNativeDriver

When using Animated.event with useNativeDriver set to false, it is required to set disableReanimated to true in the gesture configuration.

Mapping of Animated.event depends on the value of useNativeDriver property. When set to true, event data can be accessed through nativeEvent.handlerData property:

jsx
const value = useAnimatedValue(0);

const event = Animated.event(
[{ nativeEvent: { handlerData: { / translationX: value, ... / } } }],
{ useNativeDriver: true }
);

In case of useNativeDriver set to false, event data is accessed directly:

jsx
const value = useAnimatedValue(0);

const event = Animated.event(
[ { / translationX: value, ... / } ],
{ useNativeDriver: false }
);

Usage with VirtualGestureDetector

Using Animated.event with VirtualGestureDetector is possible only when useNativeDriver is set to false.

<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[8, 33]}
src={
import React from 'react';
import { View, StyleSheet, Animated, useAnimatedValue } from 'react-native';
import {
GestureHandlerRootView,
InterceptingGestureDetector,
usePanGesture,
VirtualGestureDetector,
} from 'react-native-gesture-handler';

export default function App() {
const value = useAnimatedValue(0);
const event = Animated.event([{ translationX: value }], {
useNativeDriver: false,
});

const panGesture = usePanGesture({
onUpdate: event,
disableReanimated: true,
});

return (
<GestureHandlerRootView style={styles.container}>
<InterceptingGestureDetector>
<View style={styles.outerBox}>
<VirtualGestureDetector gesture={panGesture}>
<Animated.View
style={[styles.innerBox, { transform: [{ translateX: value }] }]}
/>
</VirtualGestureDetector>
</View>
</InterceptingGestureDetector>
</GestureHandlerRootView>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
outerBox: {
backgroundColor: '#b58df1',
width: 150,
height: 150,
},
innerBox: {
width: 100,
height: 100,
backgroundColor: 'blue',
},
});
}/>

---

Packages/Docs Gesture Handler/Docs/Fundamentals/Callbacks Events (packages/docs-gesture-handler/docs/fundamentals/callbacks-events.mdx)

---
id: callbacks-events
title: Gesture callbacks & events
sidebar_label: Gesture callbacks & events
sidebar_position: 3
---

import {
GestureCallbacksExample,
TouchCallbacksExample,
} from '@site/src/examples/CallbacksFlowExamples';

At any given time, each handler instance has an assigned state that can change when new touch events occur or can be forced to change by the touch system under certain circumstances. You can hook into state transitions using specific gesture callbacks.

When Reanimated is installed, all callbacks are automatically workletized. For more details, refer to the Integration with Reanimated section.

Callbacks flow

GestureEvent callbacks

Drag or hold the circle below to see how the callback chain reacts.

<GestureCallbacksExample />

Note that some of these callbacks are complementary:

- if onBegin was called, it is guaranteed that onFinalize will be called later.
- if onActivate was called, it is guaranteed that onDeactivate will be called later.

TouchEvent callbacks

Low-level TouchEvent callbacks are tied to raw pointer activity. Drag out of the card to cancel, or release inside it to see how the callback chain reacts.

<TouchCallbacksExample />

Callbacks

onBegin

ts
onBegin: (event: GestureEvent<HandlerData>) => void

Called when a handler begins to recognize gestures. If onBegin was called, it is guaranteed that onFinalize will be called later.

onActivate

ts
onActivate: (event: GestureEvent<HandlerData>) => void

Called when activation criteria for the handler are met. If onActivate was called, it is guaranteed that onDeactivate will be called later.

onUpdate

ts
onUpdate: (event: GestureEvent<HandlerData>) => void

Called each time a pointer tracked by the gesture changes state, typically due to movement, after the gesture has been activated.

onDeactivate

ts
onDeactivate: (event: GestureEndEvent<HandlerData>) => void

Called when handler stops recognizing gestures, but only if the handler activated. It is called before onFinalize. The event object contains a canceled property — if the gesture was interrupted, canceled is set to true. Otherwise it is set to false.

onFinalize

ts
onFinalize: (event: GestureEndEvent<HandlerData>) => void

Called when handler stops recognizing gestures. The event object contains a canceled property — if the handler failed to activate or was interrupted, canceled is set to true. If the handler managed to activate and completed successfully, canceled is set to false and onFinalize will be called right after onDeactivate.

onTouchesDown

ts
onTouchesDown: (event: GestureTouchEvent) => void

Called when new pointers are placed on the screen. It may carry information about more than one pointer because the events are batched.

onTouchesMove

ts
onTouchesMove: (event: GestureTouchEvent) => void

Called when pointers are moved on the screen. It may carry information about more than one pointer because the events are batched.

onTouchesUp

ts
onTouchesUp: (event: GestureTouchEvent) => void

Called when pointers are lifted from the screen. It may carry information about more than one pointer because the events are batched.

onTouchesCancel

ts
onTouchesCancel: (event: GestureTouchEvent) => void

Called when there will be no more information about this pointer. It may be called because the gesture has ended or was interrupted. It may carry information about more than one pointer because the events are batched.

Events

GestureEvent

<CollapsibleCode
label="Show composed types definitions"
expandedLabel="Hide composed types definitions"
lineBounds={[0, 8]}
src={
export type GestureEvent<HandlerData> = {
handlerTag: number;
numberOfPointers: number;
pointerType: PointerType;
} & HandlerData;

export type GestureEndEvent<HandlerData> = {
canceled: boolean;
} & GestureEvent<HandlerData>;

export enum PointerType {
TOUCH,
STYLUS,
MOUSE,
KEY,
OTHER,
}
}/>

GestureEvent contains properties common to all gestures (handlerTag, numberOfPointers, pointerType) along with gesture-specific data defined in each gesture's documentation.

GestureEndEvent

GestureEndEvent extends GestureEvent with a canceled property. It is used in the onDeactivate and onFinalize callbacks. When canceled is true, the gesture was interrupted or failed to activate. When false, the gesture completed successfully.

TouchEvent

<CollapsibleCode
label="Show composed types definitions"
expandedLabel="Hide composed types definitions"
lineBounds={[0, 8]}
src={
export type GestureTouchEvent = {
handlerTag: number;
numberOfTouches: number;
state: State;
eventType: TouchEventType;
allTouches: TouchData[];
changedTouches: TouchData[];
pointerType: PointerType;
};

export const State = {
UNDETERMINED: 0,
FAILED: 1,
BEGAN: 2,
CANCELLED: 3,
ACTIVE: 4,
END: 5,
} as const;

export const TouchEventType = {
UNDETERMINED: 0,
TOUCHES_DOWN: 1,
TOUCHES_MOVE: 2,
TOUCHES_UP: 3,
TOUCHES_CANCEL: 4,
} as const;

export enum PointerType {
TOUCH,
STYLUS,
MOUSE,
KEY,
OTHER,
}

export type TouchData = {
id: number;
x: number;
y: number;
absoluteX: number;
absoluteY: number;
};
}/>

TouchEvent carries information about raw touch events, like touching the screen or moving the finger.

- eventType - Type of the current event - whether the finger was placed on the screen, moved, lifted or cancelled.

- changedTouches - An array of objects where every object represents a single touch. Contains information only about the touches that were affected by the event i.e. those that were placed down, moved, lifted or cancelled.

- allTouches - An array of objects where every object represents a single touch. Contains information about all active touches.

- numberOfTouches - Number representing the count of currently active touches.

TouchData contains information about a single touch.

- id - A number representing the id of the touch. It may be used to track the touch between events as the id will not change while it is being tracked.

- x - X coordinate of the current position of the touch relative to the view attached to the GestureDetector. Expressed in point units.

- y - Y coordinate of the current position of the touch relative to the view attached to the GestureDetector. Expressed in point units.

- absoluteX - X coordinate of the current position of the touch relative to the window. The value is expressed in point units. It is recommended to use it instead of x in cases when the original view can be transformed as an effect of the gesture.

- absoluteY - Y coordinate of the current position of the touch relative to the window. The value is expressed in point units. It is recommended to use it instead of y in cases when the original view can be transformed as an effect of the gesture.

:::danger
Don't rely on the order of items in the changedTouches/allTouches arrays as it may change during the gesture, instead use the id attribute to track individual touches across events.
:::

---

Packages/Docs Gesture Handler/Docs/Fundamentals/Getting Started (packages/docs-gesture-handler/docs/fundamentals/getting-started.mdx)

---
id: getting-started
title: Getting started
sidebar_position: 1
---

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

import { GestureHandlerCompatibility } from '../../components/Compatibility';

Gesture Handler provides a declarative API exposing the native platform's touch and gesture system to React Native. It's designed to be a replacement of React Native's built in touch system called Gesture Responder System. Using native touch handling allows addressing the performance limitations of React Native's Gesture Responder System. It also provides more control over the platform's native components that can handle gestures on their own.

Installation

Requirements

#### Compatibility with React Native versions

react-native-gesture-handler supports the three latest minor releases of react-native.

<GestureHandlerCompatibility spacerAfterIndex={1} />

#### Running gestures on the UI thread

Using Reanimated is the recommended method of handling gesture-driven interactions on the UI thread. In order to use it, you need to install react-native-reanimated along with react-native-worklets. Another approach is to use React Native's Animated API.

For more details on how to implement these, refer to the dedicated sections for Reanimated and Animated.

Setup

Setting up react-native-gesture-handler is pretty straightforward:

#### 1. Start with installing the package from npm:

<Tabs groupId="package-managers">
<TabItem value="npm" label="NPM" default>

bash
npm install react-native-gesture-handler

</TabItem>
<TabItem value="yarn" label="YARN">
bash
yarn add react-native-gesture-handler

</TabItem>
<TabItem value="expo" label="EXPO">
bash
npx expo install react-native-gesture-handler

</TabItem>
</Tabs>

#### 2. Wrap your app with GestureHandlerRootView component

jsx
import { GestureHandlerRootView } from 'react-native-gesture-handler';

export default function App() {
return (
<GestureHandlerRootView>
<ActualApp />
</GestureHandlerRootView>
);
}


Keep GestureHandlerRootView as close to the actual root of the app as possible. It's the entry point for all gestures and all gesture relations. The gestures won't be recognized outside of the root view, and relations only work between gestures mounted under the same root view.

Check out GestureHandlerRootView section for more details.

#### 3. Platform specific setup

#### Expo development build

When using an Expo development build, run prebuild to update the native code in the ios and android directories.

bash
npx expo prebuild

##### Android

Setting up Gesture Handler on Android doesn't require any more steps. Keep in mind that if you want to use gestures in Modals you need to wrap Modal's content with GestureHandlerRootView:

jsx
import { Modal } from 'react-native';
import { GestureHandlerRootView } from 'react-native-gesture-handler';

export function CustomModal({ children, ...rest }) {
return (
<Modal {...rest}>
<GestureHandlerRootView>{children}</GestureHandlerRootView>
</Modal>
);
}

##### iOS

While developing for iOS, make sure to install pods first before running the app:

bash
cd ios && bundle install && bundle exec pod install && cd ..

##### macOS

While developing for macOS, make sure to install pods first before running the app:

bash
cd macos && bundle install && bundle exec pod install && cd ..

##### Web

There is no additional configuration required for the web.

##### With wix/react-native-navigation

If you are using a native navigation library like wix/react-native-navigation you need to make sure that every screen is wrapped with GestureHandlerRootView. This can be done for example at the stage when you register your screens. Here's an example:

js
import { Navigation } from 'react-native-navigation';
import FirstTabScreen from './FirstTabScreen';
import SecondTabScreen from './SecondTabScreen';
import PushedScreen from './PushedScreen';

// Register all screens of the app (including internal ones)
export function registerScreens() {
Navigation.registerComponent(
'example.FirstTabScreen',
() => {
return (
<GestureHandlerRootView>
<FirstTabScreen />
</GestureHandlerRootView>
);
},
() => FirstTabScreen
);
Navigation.registerComponent(
'example.SecondTabScreen',
() => {
return (
<GestureHandlerRootView>
<SecondTabScreen />
</GestureHandlerRootView>
);
},
() => SecondTabScreen
);
Navigation.registerComponent(
'example.PushedScreen',
() => {
return (
<GestureHandlerRootView>
<PushedScreen />
</GestureHandlerRootView>
);
},
() => PushedScreen
);
}

You can check out this example project to see this kind of set up in action.

Your first gesture

With the setup done, you're ready to add your first gesture. Create a tap gesture with the useTapGesture hook, attach it to a component with GestureDetector. Don't forget to wrap your app with GestureHandlerRootView, as described in previous section.

<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[1, 21]}
src={
import { StyleSheet, View } from 'react-native';
import {
GestureDetector,
GestureHandlerRootView,
useTapGesture,
} from 'react-native-gesture-handler';

export default function App() {
const tap = useTapGesture({
onActivate: () => {
console.log('Tapped!');
},
});

return (
<GestureHandlerRootView style={styles.container}>
<GestureDetector gesture={tap}>
<View style={styles.box} />
</GestureDetector>
</GestureHandlerRootView>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
box: {
width: 80,
height: 80,
backgroundColor: '#b58df1',
borderRadius: 12,
},
});
}/>

That's it! The gesture fires onActivate whenever a tap is recognized. Check out Gesture callbacks & events for the full set of callbacks, or browse all gestures to keep exploring.

---

Packages/Docs Gesture Handler/Docs/Fundamentals/Reanimated Interactions (packages/docs-gesture-handler/docs/fundamentals/reanimated-interactions.mdx)

---
id: reanimated-interactions
title: Integration with Reanimated
sidebar_label: Integration with Reanimated
sidebar_position: 4
---

GestureDetector will decide whether to use Reanimated to process provided gestures based on their configuration. If any of the callbacks is a worklet and Reanimated is not explicitly turned off, tools provided by the Reanimated will be utilized, bringing the ability to handle gestures synchronously on the main thread.

Automatic workletization of gesture callbacks

Worklets' Babel plugin is set up in a way that automatically marks callbacks passed to gestures in the configuration chain as worklets. This means that you don't need to add a 'worklet'; directive at the beginning of the functions. Here is an example that will be automatically workletized:

jsx
const gesture = useTapGesture({
onBegin: () => {
console.log(_WORKLET);
},
});

And here is one that won't:

jsx
const callback = () => {
console.log(_WORKLET);
};

const gesture = useTapGesture({
onBegin: callback,
});

It also won't work when wrapped with hooks like useCallback or useMemo, e.g.:

jsx
const callback = useCallback(() => {
console.log(_WORKLET);
}, []);

const gesture = useTapGesture({
onBegin: callback,
});

In the above cases, you should add a "worklet"; directive at the beginning of the callbacks, like so:

jsx
const callback = () => {
// highlight-next-line
'worklet';
console.log(_WORKLET);
};

const gesture = useTapGesture({
onBegin: callback,
});

jsx
const callback = useCallback(() => {
// highlight-next-line
'worklet';
console.log(_WORKLET);
}, []);

const gesture = useTapGesture({
onBegin: callback,
});

Using SharedValue in gesture config

RNGH3 allows passing SharedValue to gestures' configurations. This allows reacting to configuration changes without unnecessary rerenders.

<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[10, 19]}
src={
import * as React from 'react';
import { Animated } from 'react-native';
import {
GestureHandlerRootView,
GestureDetector,
useTapGesture,
} from 'react-native-gesture-handler';
import { useSharedValue } from 'react-native-reanimated';

export default function App() {
// highlight-next-line
const taps = useSharedValue(2);

const gesture = useTapGesture({
// highlight-next-line
numberOfTaps: taps,
onDeactivate: () => {
taps.value += 1;
},
});

return (
<GestureHandlerRootView>
<GestureDetector gesture={gesture}>
<Animated.View
style={{
width: 150,
height: 150,
backgroundColor: 'blue',
}}
/>
</GestureDetector>
</GestureHandlerRootView>
);
}
}/>

Disabling Reanimated

Gestures created with the hook API have Reanimated integration enabled by default, meaning all callbacks are executed on the UI thread. There are two methods available to disable this behavior for a specific gesture.

disableReanimated

When disableReanimated is set to true in the gesture configuration, Reanimated integration will be completely turned off for that gesture throughout its entire lifecycle. This setting eliminates all interaction points with Reanimated, thereby reducing any potential overhead. Default value for this property is false.

This property cannot be changed dynamically during the gesture's lifecycle.

jsx
const gesture = usePanGesture({
// highlight-next-line
disableReanimated: true,

onUpdate: () => {
console.log('Panning');
},
});

runOnJS

The runOnJS property allows you to dynamically control whether callbacks are executed on the JS thread or the UI thread. When set to true, callbacks will run on the JS thread. Setting it to false will execute them on the UI thread. Default value for this property is false.

This property can be changed dynamically throughout the gesture's lifecycle.

<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[9, 37]}
src={
import React from 'react';
import { View, StyleSheet, Animated } from 'react-native';
import {
GestureDetector,
GestureHandlerRootView,
usePanGesture,
} from 'react-native-gesture-handler';
import { useSharedValue } from 'react-native-reanimated';

export default function App() {
// highlight-next-line
const shouldRunOnJS = useSharedValue(false);

const panGesture = usePanGesture({
onUpdate: () => {
console.log(
globalThis.__RUNTIME_KIND === 2
? 'Running on UI thread'
: 'Running on JS thread'
);
},
onDeactivate: () => {
shouldRunOnJS.value = !shouldRunOnJS.value;
},
// highlight-next-line
runOnJS: shouldRunOnJS,
});

return (
<GestureHandlerRootView style={styles.container}>
<View style={styles.outerBox}>
<GestureDetector gesture={panGesture}>
<Animated.View style={styles.innerBox} />
</GestureDetector>
</View>
</GestureHandlerRootView>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
outerBox: {
backgroundColor: '#b58df1',
width: 150,
height: 150,
},
innerBox: {
width: 100,
height: 100,
backgroundColor: 'blue',
},
});
}/>

---

Packages/Docs Gesture Handler/Docs/Fundamentals/Gesture Animation/Index (packages/docs-gesture-handler/docs/fundamentals/gesture-animation/index.md)

---
id: gesture-animation
title: Your first gesture-driven animation
sidebar_label: Your first gesture-driven animation
sidebar_position: 2
---

import Step, { Divider } from '@site/src/theme/Step';
import Step1 from './\_steps/step1.md';
import Step2 from './\_steps/step2.md';
import Step3 from './\_steps/step3.md';
import Step4 from './\_steps/step4.md';
import Step5 from './\_steps/step5.md';

RNGH3 offers a straightforward way to add gestures to your app. Simply wrap your target view with the GestureDetector component, define your gesture, and pass it in. That’s it!

To see the new API in action, let's build a simple app where you can drag a ball around the screen. To follow along, you'll need both react-native-gesture-handler (to handle gestures) and react-native-reanimated (to handle the animations).

<Step title="Step 1">
<div>Start by defining the basic structure of the application:</div>
<Step1 />
</Step>

<Step title="Step 2">
<div>
Next, define the <a href="https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/glossary#shared-value" target="_blank">SharedValues</a> to track the ball's position and create the animated styles required to position the ball on the screen:
</div>
<Step2 />
</Step>

<Step title="Step 3">
<div>Apply the animated styles to the ball component:</div>
<Step3 />
</Step>

<Step title="Step 4">
<div>
Now, define the <code>Pan</code> gesture logic.
</div>
<Step4 />
</Step>

<Step title="Step 5">
<div>
Finally, wrap the component responsible for rendering the ball with a <code>GestureDetector</code>, and attach the <code>Pan</code> gesture to it:
</div>
<Step5 />
</Step>

The complete implementation is shown below:

jsx
import { StyleSheet } from 'react-native';
import {
GestureDetector,
GestureHandlerRootView,
usePanGesture,
} from 'react-native-gesture-handler';
import Animated, {
useAnimatedStyle,
useSharedValue,
withSpring,
} from 'react-native-reanimated';

export default function Ball() {
const isPressed = useSharedValue(false);
const offset = useSharedValue({ x: 0, y: 0 });

const gesture = usePanGesture({
onBegin: () => {
isPressed.value = true;
},
onUpdate: (e) => {
offset.value = {
x: offset.value.x + e.changeX,
y: offset.value.y + e.changeY,
};
},
onFinalize: () => {
isPressed.value = false;
},
});

const animatedStyles = useAnimatedStyle(() => {
return {
transform: [
{ translateX: offset.value.x },
{ translateY: offset.value.y },
{ scale: withSpring(isPressed.value ? 1.2 : 1) },
],
backgroundColor: isPressed.value ? 'yellow' : 'blue',
};
});

return (
<GestureHandlerRootView>
<GestureDetector gesture={gesture}>
<Animated.View style={[styles.ball, animatedStyles]} />
</GestureDetector>
</GestureHandlerRootView>
);
}

const styles = StyleSheet.create({
ball: {
width: 100,
height: 100,
borderRadius: 100,
backgroundColor: 'blue',
alignSelf: 'center',
},
});

---

Packages/Docs Gesture Handler/Docs/Fundamentals/Gesture Animation/ Steps/Step1 (packages/docs-gesture-handler/docs/fundamentals/gesture-animation/_steps/step1.md)

jsx
import { StyleSheet } from 'react-native';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import Animated from 'react-native-reanimated';

export default function Ball() {
return (
<GestureHandlerRootView>
<Animated.View style={styles.ball} />
</GestureHandlerRootView>
);
}

const styles = StyleSheet.create({
ball: {
width: 100,
height: 100,
borderRadius: 100,
backgroundColor: 'blue',
alignSelf: 'center',
},
});

---

Packages/Docs Gesture Handler/Docs/Fundamentals/Gesture Animation/ Steps/Step2 (packages/docs-gesture-handler/docs/fundamentals/gesture-animation/_steps/step2.md)

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

export default function Ball() {
const isPressed = useSharedValue(false);
const offset = useSharedValue({ x: 0, y: 0 });

const animatedStyles = useAnimatedStyle(() => {
return {
transform: [
{ translateX: offset.value.x },
{ translateY: offset.value.y },
{ scale: withSpring(isPressed.value ? 1.2 : 1) },
],
backgroundColor: isPressed.value ? 'yellow' : 'blue',
};
});

// ...
}

---

Packages/Docs Gesture Handler/Docs/Fundamentals/Gesture Animation/ Steps/Step3 (packages/docs-gesture-handler/docs/fundamentals/gesture-animation/_steps/step3.md)

``jsx {4}
// ...
return (
<GestureHandlerRootView>
<Animated.View style={[styles.ball, animatedStyles]} />
</GestureHandlerRootView>
);
// ...

text
---

Packages/Docs Gesture Handler/Docs/Fundamentals/Gesture Animation/ Steps/Step4 (packages/docs-gesture-handler/docs/fundamentals/gesture-animation/_steps/step4.md)

jsx
import { usePanGesture } from 'react-native-gesture-handler';

function Ball() {
// ...
const gesture = usePanGesture({
onBegin: () => {
isPressed.value = true;
},
onUpdate: (e) => {
offset.value = {
x: offset.value.x + e.changeX,
y: offset.value.y + e.changeY,
};
},
onFinalize: () => {
isPressed.value = false;
},
});
// ...
}

text
---

Packages/Docs Gesture Handler/Docs/Fundamentals/Gesture Animation/ Steps/Step5 (packages/docs-gesture-handler/docs/fundamentals/gesture-animation/_steps/step5.md)

jsx {4}
// ...
return (
<GestureHandlerRootView>
<GestureDetector gesture={gesture}>
<Animated.View style={[styles.ball, animatedStyles]} />
</GestureDetector>
</GestureHandlerRootView>
);
// ...
text
---

Packages/Docs Gesture Handler/Docs/Gestures/ Category .Json (packages/docs-gesture-handler/docs/gestures/_category_.json)

{
"label": "Gestures API",
"position": 4,
"link": {
"type": "generated-index"
}
}

---

Packages/Docs Gesture Handler/Docs/Gestures/State Manager (packages/docs-gesture-handler/docs/gestures/state-manager.mdx)

---
id: state-manager
title: Gesture state manager
sidebar_label: Gesture state manager
sidebar_position: 10
---

RNGH3 allows manually controlling the gestures' lifecycle by using GestureStateManager.

State management

Manual state management is based on handlerTag. There are two ways of manual state control. Some gestures also support manualActivation property, which blocks their automatic activation, even if they meet their activation criteria.

Inside gesture definition

If you want to manipulate gesture's state in its callbacks, you can get handlerTag from event parameter.

<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[9, 17]}
src={

import { StyleSheet, View } from 'react-native';
import {
GestureDetector,
GestureHandlerRootView,
GestureStateManager,
useLongPressGesture,
} from 'react-native-gesture-handler';

export default function App() {
const longPress = useLongPressGesture({
onTouchesDown: (e) => {
// highlight-next-line
GestureStateManager.activate(e.handlerTag);
},
onActivate: () => {
console.log('LongPress activated!');
},
});

return (
<GestureHandlerRootView style={styles.container}>
<GestureDetector gesture={longPress}>
<View style={styles.box} />
</GestureDetector>
</GestureHandlerRootView>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'space-around',
},

box: {
width: 150,
height: 150,
backgroundColor: 'blue',
},
});
}/>


Outside gesture definition

If you want to control gesture lifecycle outside of it, you can use handlerTag from created gesture object.

:::note
The gestures can only be activated after they have begun, that is, after they have received touch events.
:::

<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[10, 21]}
src={

import { StyleSheet, View } from 'react-native';
import {
GestureDetector,
GestureHandlerRootView,
GestureStateManager,
useLongPressGesture,
usePanGesture,
} from 'react-native-gesture-handler';

export default function App() {
const pan = usePanGesture({
onActivate: () => {
console.log('Pan activated!');
},
});

const longPress = useLongPressGesture({
onActivate: () => {
// highlight-next-line
GestureStateManager.activate(pan.handlerTag);
},
});

return (
<GestureHandlerRootView style={styles.container}>
<GestureDetector gesture={longPress}>
<View style={styles.box} />
</GestureDetector>
<GestureDetector gesture={pan}>
<View style={styles.box} />
</GestureDetector>
</GestureHandlerRootView>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'space-around',
},

box: {
width: 150,
height: 150,
backgroundColor: 'blue',
},
});
}/>


manualActivation

When manualActivation property is set to true on a gesture, it will not activate automatically even if its activation criteria are met. Use GestureStateManager to manipulate its state manually.

In the example below, Pan gesture will not activate by simply panning on the blue box - using GestureStateManager is necessary.

<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[7, 22]}
src={

import { View } from 'react-native';
import {
GestureDetector,
GestureHandlerRootView,
usePanGesture,
} from 'react-native-gesture-handler';

export default function App() {
const pan = usePanGesture({
onActivate: () => {
console.log('Pan gesture activated');
},
manualActivation: true,
});

return (
<GestureHandlerRootView style={styles.container}>
<GestureDetector gesture={pan}>
<View style={styles.box} />
</GestureDetector>
</GestureHandlerRootView>
);
}

const styles = {
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'space-around',
},
box: {
width: 150,
height: 150,
backgroundColor: 'blue',
},
};
}/>

GestureStateManager

GestureStateManager provides methods to manipulate gesture's state imperatively.

activate

tsx
activate: (handlerTag: number) => void;
text
Triggers onActivate callback on the gesture with the specified handlerTag.

deactivate

tsx
deactivate: (handlerTag: number) => void;
text
If the gesture had activated, it triggers the onDeactivate callback. It also triggers the onFinalize callback on the gesture with the specified handlerTag.

fail

tsx
fail: (handlerTag: number) => void;
text
Triggers onFinalize callback on the gesture with the specified handlerTag. If the gesture had activated, it will also trigger onDeactivate callback.

---

Packages/Docs Gesture Handler/Docs/Gestures/Use Fling Gesture (packages/docs-gesture-handler/docs/gestures/use-fling-gesture.mdx)

---
id: use-fling-gesture
title: Fling gesture
sidebar_label: Fling gesture
sidebar_position: 6
---

import { webContainer } from '@site/src/utils/getGestureStyles';

import FlingGestureBasic from '@site/static/examples/FlingGestureBasic';
import FlingGestureBasicSrc from '!!raw-loader!@site/static/examples/FlingGestureBasic';


import MouseButtonProp from './\_shared/mouse-button.mdx';

The gesture that tracks quick, sufficiently long movement in specified direction.

<div className={webContainer}>
<InteractiveExample
component={<FlingGestureBasic/>}
src={FlingGestureBasicSrc}
disableMarginBottom={true}
/>
</div>

import BaseEventData from './\_shared/base-gesture-event-data.mdx';
import BaseGestureConfig from './\_shared/base-gesture-config.mdx';
import BaseGestureCallbacks from './\_shared/base-gesture-callbacks.mdx';
import SharedValueInfo from './\_shared/shared-value-info.md';

When the gesture gets activated it will end when the finger is released.
The gesture will fail if the finger is lifted before the gesture could activate.

Example

<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[13, 34]}
src={

import { StyleSheet } from 'react-native';
import {
GestureDetector,
GestureHandlerRootView,
Directions,
useFlingGesture,
} from 'react-native-gesture-handler';
import Animated, {
useSharedValue,
useAnimatedStyle,
withTiming,
} from 'react-native-reanimated';

export default function App() {
const position = useSharedValue(0);

const flingGesture = useFlingGesture({
direction: Directions.RIGHT,
onActivate: () => {
position.value = withTiming(position.value + 10, { duration: 100 });
},
});

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

return (
<GestureHandlerRootView style={styles.container}>
<GestureDetector gesture={flingGesture}>
<Animated.View style={[styles.box, animatedStyle]} />
</GestureDetector>
</GestureHandlerRootView>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'space-around',
},
box: {
height: 120,
width: 120,
backgroundColor: '#b58df1',
borderRadius: 20,
marginBottom: 30,
},
});
}/>

Config

<SharedValueInfo />

direction

<CollapsibleCode
label="Show composed types definitions"
expandedLabel="Hide composed types definitions"
lineBounds={[0, 1]}
src={

direction: Directions | SharedValue<Directions>;

export const Directions = {
RIGHT: 1,
LEFT: 2,
UP: 4,
DOWN: 8,
} as const;
}/>

Expresses the allowed direction of movement. Expected values are exported as constants in the Directions object. It's possible to combine directions using | operator.

js
import { Directions } from 'react-native-gesture-handler';

// Single direction
const fling = useFlingGesture({ direction: Directions.RIGHT });

// Combined directions
const fling = useFlingGesture({ direction: Directions.RIGHT | Directions.LEFT });

text

numberOfPointers

ts
numberOfPointers: number | SharedValue<number>;
text
Determines the exact number of pointers required to handle the fling gesture.

<MouseButtonProp />

<BaseGestureConfig />

Callbacks

<BaseGestureCallbacks gesture={"Fling"}/>

Event data

x

ts
x: number;
text
X coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the GestureDetector. Expressed in point units.

y

ts
y: number;
text
Y coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the GestureDetector. Expressed in point units.

absoluteX

ts
absoluteX: number;
text
X coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the window. The value is expressed in point units. It is recommended to use it instead of x in cases when the original view can be transformed as an effect of the gesture.

absoluteY

ts
absoluteY: number;
text
Y coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the window. The value is expressed in point units. It is recommended to use it instead of y in cases when the original view can be transformed as an effect of the gesture.

<BaseEventData />

---

Packages/Docs Gesture Handler/Docs/Gestures/Use Hover Gesture (packages/docs-gesture-handler/docs/gestures/use-hover-gesture.mdx)

---
id: use-hover-gesture
title: Hover gesture
sidebar_label: Hover gesture
sidebar_position: 7
---

import { webContainer } from '@site/src/utils/getGestureStyles';

import HoverGestureBasic from '@site/static/examples/HoverGestureBasic';
import HoverGestureBasicSrc from '!!raw-loader!@site/static/examples/HoverGestureBasic';

Gesture that can recognize hovering above the view it's attached to.

<div className={webContainer}>
<InteractiveExample
component={<HoverGestureBasic/>}
src={HoverGestureBasicSrc}
disableMarginBottom={true}
/>
</div>

import BaseEventData from './\_shared/base-gesture-event-data.mdx';
import BaseGestureConfig from './\_shared/base-gesture-config.mdx';
import BaseGestureCallbacks from './\_shared/base-gesture-callbacks.mdx';
import BaseContinuousGestureCallbacks from './\_shared/base-continuous-gesture-callbacks.mdx';
import SharedValueInfo from './\_shared/shared-value-info.md';

The hover effect may be activated by moving a mouse or a stylus over the view.

On iOS additional visual effects may be configured.

:::note
Don't rely on
Hover gesture to continue after the mouse button is clicked or the stylus touches the screen. If you want to handle both cases, compose it with Pan gesture.
:::

Example

<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[7, 17]}
src={

import { View, StyleSheet } from 'react-native';
import {
GestureDetector,
GestureHandlerRootView,
useHoverGesture,
} from 'react-native-gesture-handler';

export default function App() {
const hoverGesture = useHoverGesture({});

return (
<GestureHandlerRootView style={styles.container}>
<GestureDetector gesture={hoverGesture}>
<View style={styles.box} />
</GestureDetector>
</GestureHandlerRootView>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'space-around',
},
box: {
height: 120,
width: 120,
backgroundColor: '#b58df1',
borderRadius: 20,
marginBottom: 30,
},
});
}/>

Config

<SharedValueInfo />

<Badges platforms={['ios']}>

effect [I]


</Badges>

<CollapsibleCode
label="Show composed types definitions"
expandedLabel="Hide composed types definitions"
lineBounds={[0, 1]}
collapsed={false}
src={

effect: HoverEffect | SharedValue<HoverEffect>;

enum HoverEffect {
NONE = 0,
LIFT = 1,
HIGHLIGHT = 2,
}
}/>

Visual effect applied to the view while the view is hovered. Defaults to HoverEffect.None.

<BaseGestureConfig />

Callbacks

<BaseGestureCallbacks gesture={"Hover"}/>
<BaseContinuousGestureCallbacks gesture={"Hover"}/>

Event data

x

ts
x: number;
text
X coordinate of the current position of the pointer relative to the view attached to the GestureDetector. Expressed in point units.

y

ts
y: number;
text
Y coordinate of the current position of the pointer relative to the view attached to the GestureDetector. Expressed in point units.

absoluteX

ts
absoluteX: number;
text
X coordinate of the current position of the pointer relative to the window. The value is expressed in point units. It is recommended to use it instead of x in cases when the original view can be transformed as an effect of the gesture.

absoluteY

ts
absoluteY: number;
text
Y coordinate of the current position of the pointer relative to the window. The value is expressed in point units. It is recommended to use it instead of y in cases when the original view can be transformed as an effect of the gesture.

stylusData

ts
stylusData: StylusData;
text
ts
interface StylusData {
tiltX: number;
tiltY: number;
azimuthAngle: number;
altitudeAngle: number;
pressure: number;
}
text
Object that contains additional information about stylus. It consists of the following fields:

- tiltX - angle in degrees between the Y-Z plane of the stylus and the screen.
-
tiltY - angle in degrees between the X-Z plane of the stylus and the screen.
-
altitudeAngle - angle between stylus axis and the X-Y plane of a device screen.
-
azimuthAngle - angle between the Y-Z plane and the plane containing both the stylus axis and the Y axis.
-
pressure - indicates the normalized pressure of the stylus.

<BaseEventData />

---

Packages/Docs Gesture Handler/Docs/Gestures/Use Long Press Gesture (packages/docs-gesture-handler/docs/gestures/use-long-press-gesture.mdx)

---
id: use-long-press-gesture
title: Long press gesture
sidebar_label: Long press gesture
sidebar_position: 3
---

import { webContainer } from '@site/src/utils/getGestureStyles';

import LongPressGestureBasic from '@site/static/examples/LongPressGestureBasic';
import LongPressGestureBasicSrc from '!!raw-loader!@site/static/examples/LongPressGestureBasic';

import MouseButtonProp from './\_shared/mouse-button.mdx';

Gesture that activates when the corresponding view is pressed for a sufficiently long time.

<div className={webContainer}>
<InteractiveExample
component={<LongPressGestureBasic/>}
src={LongPressGestureBasicSrc}
disableMarginBottom={true}
/>
</div>

import BaseEventData from './\_shared/base-gesture-event-data.mdx';
import BaseGestureConfig from './\_shared/base-gesture-config.mdx';
import BaseGestureCallbacks from './\_shared/base-gesture-callbacks.mdx';
import SharedValueInfo from './\_shared/shared-value-info.md';

import LongPressExample from '!!raw-loader!./_examples/LongPressExample';

This gesture will deactivate immediately after the finger is released.
The gesture will fail to recognize a touch event if the finger is lifted before the minimum required time or if the finger is moved further than the allowable distance.

Example

<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[7, 23]}
src={LongPressExample}/>


Config

<SharedValueInfo />

minDuration

ts
minDuration: number | SharedValue<number>;
text
Minimum time, expressed in milliseconds, that a finger must remain pressed on the corresponding view. The default value is 500.

maxDistance

ts
maxDistance: number | SharedValue<number>;
text
Maximum distance, expressed in points, that defines how far the finger is allowed to travel during a long press gesture. If the finger travels further than the defined distance and the gesture hasn't yet activated, it will fail to recognize the gesture. The default value is 10.

<MouseButtonProp />

<BaseGestureConfig />

Callbacks

<BaseGestureCallbacks gesture={"LongPress"}/>

Event data

x

ts
x: number;
text
X coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the GestureDetector.

y

ts
y: number;
text
Y coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the GestureDetector.

absoluteX

ts
absoluteX: number;
text
X coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the window. It is recommended to use absoluteX instead of x in cases when the view attached to the GestureDetector can be transformed as an effect of the gesture.

absoluteY

ts
absoluteY: number;
text
Y coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the window. It is recommended to use absoluteY instead of y in cases when the view attached to the GestureDetector can be transformed as an effect of the gesture.

duration

ts
duration: number;
text
Duration of the long press (time since the start of the gesture), expressed in milliseconds.

<BaseEventData />

---

Packages/Docs Gesture Handler/Docs/Gestures/Use Manual Gesture (packages/docs-gesture-handler/docs/gestures/use-manual-gesture.mdx)

---
id: use-manual-gesture
title: Manual gesture
sidebar_label: Manual gesture
sidebar_position: 9
---

import BaseEventData from './\_shared/base-gesture-event-data.mdx';
import BaseGestureConfig from './\_shared/base-gesture-config.mdx';
import BaseGestureCallbacks from './\_shared/base-gesture-callbacks.mdx';
import BaseContinuousGestureCallbacks from './\_shared/base-continuous-gesture-callbacks.mdx';
import SharedValueInfo from './\_shared/shared-value-info.md';

import Step, { Divider } from '@site/src/theme/Step';
import Step1 from './\_manual_gesture_steps/step1.md';
import Step2 from './\_manual_gesture_steps/step2.md';
import Step3 from './\_manual_gesture_steps/step3.md';
import Step4 from './\_manual_gesture_steps/step4.md';
import Step5 from './\_manual_gesture_steps/step5.md';
import Step6 from './\_manual_gesture_steps/step6.md';
import Step7 from './\_manual_gesture_steps/step7.md';


A plain gesture that has no specific activation criteria nor event data set. Its state has to be controlled manually using a state manager. It will not fail when all the pointers are lifted from the screen.

:::tip
If you need to modify the activation criteria of gestures other than
Manual, check their configuration for relevant properties or use manualActivation to manage their state directly.
:::

Example

To demonstrate how to make a manual gesture we will make a simple one that tracks all pointers on the screen.

<Step title="Step 1">
First, we need a way to store information about the pointer: whether it should be visible and its position.
<Step1 />
</Step>

<Step title="Step 2">
We also need a component to mark where a pointer is. In order to accomplish that we will make a component that accepts two shared values: one holding information about the pointer using the interface we just created, the other holding a bool indicating whether the gesture has activated.
In this example when the gesture is not active, the ball representing it will be blue and when it is active the ball will be red and slightly bigger.
<Step2 />
</Step>

<Step title="Step 3">
Now we have to make a component that will handle the gesture and draw all the pointer indicators. We will store data about pointers in an array and render them inside an
Animated.View.
<Step3 />
</Step>

<Step title="Step 4">
We have our components set up and we can finally get to making the gesture! We will start with
onTouchesDown where we need to set position of the pointers and make them visible. We can get this information from the touches property of the event. In this case we will also check how many pointers are on the screen and activate the gesture if there are at least two.
<Step4 />
</Step>

<Step title="Step 5">
Next, we will handle pointer movement. In
onTouchesMove we will simply update the position of moved pointers.
<Step5 />
</Step>

<Step title="Step 6">
We also need to handle lifting fingers from the screen, which corresponds to
onTouchesUp. Here we will just hide the pointers that were lifted and end the gesture if there are no more pointers on the screen.
Note that we are not handling
onTouchesCancel as in this very basic case we don't expect it to happen; however, you should clear data about cancelled pointers (most of the time all active ones) when it is called.
<Step6 />
</Step>

<Step title="Step 7">
Now that our pointers are being tracked correctly and we have the state management, we can handle activation and ending of the gesture. In our case, we will simply set the active shared value either to
true or false.
<Step7 />
</Step>

<details>
<summary>Full example code</summary>


/ Detailed source-code truncated for AI context efficiency. /
text
</details>


Modifying existing gestures

While manual gestures open great possibilities, we are aware that reimplementing pinch or rotation from scratch just because you need to activate in specific circumstances or require position of the fingers, would be a waste of time as those gestures are already available. Therefore, you can use touch events with every gesture to extract more detailed information about the gesture than what the basic events alone provide. We also added a manualActivation modifier on all continuous gestures, which prevents the gesture it is applied to from activating automatically, giving you full control over its behavior.

Config

<SharedValueInfo />

<BaseGestureConfig />

Callbacks

<BaseGestureCallbacks gesture={"Manual"}/>
<BaseContinuousGestureCallbacks gesture={"Manual"} />

Event data

<BaseEventData />

---

Packages/Docs Gesture Handler/Docs/Gestures/Use Native Gesture (packages/docs-gesture-handler/docs/gestures/use-native-gesture.mdx)

---
id: use-native-gesture
title: Native gesture
sidebar_label: Native gesture
sidebar_position: 8
---

import BaseEventData from './\_shared/base-gesture-event-data.mdx';
import BaseGestureConfig from './\_shared/base-gesture-config.mdx';
import BaseGestureCallbacks from './\_shared/base-gesture-callbacks.mdx';
import BaseContinuousGestureCallbacks from './\_shared/base-continuous-gesture-callbacks.mdx';
import SharedValueInfo from './\_shared/shared-value-info.md';

A gesture that allows other touch handling components to work within RNGH's gesture system. This streamlines interactions between gestures and the native component, allowing it to form relations with other gestures.

When used, the native component should be the direct child of a GestureDetector.

:::danger
Do not use
Native gesture with components exported by React Native Gesture Handler, as they already have it pre-applied. Attaching Native gesture twice will result in undefined behavior.
:::

Example

This example renders a ScrollView with multiple colored rectangles, where each rectangle has a black section. Starting a touch on a black section will disable the ScrollView for the duration of the Pan gesture.


<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[15, 54]}
src={

import { View, ScrollView } from 'react-native';
import {
GestureDetector,
GestureHandlerRootView,
useNativeGesture,
usePanGesture,
NativeGesture,
} from 'react-native-gesture-handler';

const COLORS = ['red', 'green', 'blue', 'purple', 'orange', 'cyan'];

type RectangleProps = {
color: string;
scrollGesture: NativeGesture;
};

function Rectangle({ color, scrollGesture }: RectangleProps) {
const pan = usePanGesture({
block: scrollGesture,
});

return (
<>
<View
key={color}
style={{ width: '100%', height: 250, backgroundColor: color }}
/>
<GestureDetector gesture={pan}>
<View style={{ width: '100%', height: 50, backgroundColor: 'black' }} />
</GestureDetector>
</>
);
}

export default function App() {
const nativeGesture = useNativeGesture({});

return (
<GestureHandlerRootView>
<GestureDetector gesture={nativeGesture}>
<ScrollView style={{ flex: 1 }}>
<View>
{COLORS.map((color) => (
<Rectangle
key={color}
color={color}
scrollGesture={nativeGesture}
/>
))}
</View>
</ScrollView>
</GestureDetector>
</GestureHandlerRootView>
);
}
}/>

Remarks

- Native gesture can be used as part of gesture composition and cross-component interactions just like any other gesture. You can use this to block a native component for the duration of the gesture or to make it work alongside a gesture.

- Due to platform API limitations, the Native gesture has restricted functionality on web. For instance, it cannot be used to block scrolling on a ScrollView.

Config

<SharedValueInfo />

<Badges platforms={['android']}>

shouldActivateOnStart [A]


</Badges>
ts
shouldActivateOnStart: boolean | SharedValue<boolean>;
text
When true, the underlying handler will activate unconditionally when it receives any touches.

disallowInterruption

ts
disallowInterruption: boolean | SharedValue<boolean>;
text
When true, this handler cancels all other gesture handlers when it activates.

yieldsToContinuousGestures

ts
yieldsToContinuousGestures: boolean | SharedValue<boolean>;
text
Composes with disallowInterruption. When both are true, this handler still cancels discrete gestures (Tap, LongPress, Fling) on activation but allows continuous gestures (Pan, Pinch, Rotation, Native, Manual, Hover) to interrupt it. No-op when disallowInterruption is false. Defaults to false.

<Badges platforms={['android', 'ios']}>

delaysChildPressedState [A][I]


</Badges>
ts
delaysChildPressedState: boolean | SharedValue<boolean>;
text
When true, the wrapped scrollable container delays displaying the pressed state of its children until it's clear that the gesture is not a scroll. Set it to false to display the pressed state immediately. Defaults to true.

On iOS this controls delaysContentTouches on the underlying UIScrollView. On Android it controls whether the container delays the pressed state of its children (see shouldDelayChildPressedState) — on Android, this requires React Native 0.87 or newer and is a no-op on older versions.

<BaseGestureConfig />

Callbacks

<BaseGestureCallbacks gesture={"NativeView"}/>

Event data

pointerInside

ts
pointerInside: boolean;
text
true if the gesture was performed inside the containing view, false otherwise.

<BaseEventData />

---

Packages/Docs Gesture Handler/Docs/Gestures/Use Pan Gesture (packages/docs-gesture-handler/docs/gestures/use-pan-gesture.mdx)

---
id: use-pan-gesture
title: Pan gesture
sidebar_label: Pan gesture
sidebar_position: 1
---

import { vanishOnMobile, appearOnMobile, webContainer } from '@site/src/utils/getGestureStyles';

import useBaseUrl from '@docusaurus/useBaseUrl';

import PanGestureBasic from '@site/static/examples/PanGestureBasic';
import PanGestureBasicSrc from '!!raw-loader!@site/static/examples/PanGestureBasic';

import BaseEventData from './\_shared/base-gesture-event-data.mdx';
import BaseGestureConfig from './\_shared/base-gesture-config.mdx';
import BaseContinuousGestureConfig from './\_shared/base-continuous-gesture-config.md';
import BaseGestureCallbacks from './\_shared/base-gesture-callbacks.mdx';
import BaseContinuousGestureCallbacks from './\_shared/base-continuous-gesture-callbacks.mdx';
import SharedValueInfo from './\_shared/shared-value-info.md';
import MouseButtonProp from './\_shared/mouse-button.mdx';

A continuous gesture that can recognize a panning (dragging) gesture and track its movement.

<div className={webContainer}>
<InteractiveExample
component={<PanGestureBasic/>}
src={PanGestureBasicSrc}
disableMarginBottom={true}
/>
</div>


The gesture activates when a finger is placed on the screen and moved some initial distance.

Configurations such as a minimum initial distance, specific vertical or horizontal pan detection and number of fingers required for activation (allowing for multifinger swipes) may be specified.

Gesture callback can be used for continuous tracking of the pan gesture. It provides information about the gesture such as its XY translation from the starting point as well as its instantaneous velocity.

Example

<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[12, 35]}
src={

import { StyleSheet } from 'react-native';
import {
GestureDetector,
GestureHandlerRootView,
usePanGesture,
} from 'react-native-gesture-handler';
import Animated, {
useSharedValue,
withTiming,
useAnimatedStyle,
} from 'react-native-reanimated';

export default function App() {
const position = useSharedValue(0);

const panGesture = usePanGesture({
onUpdate: (e) => {
position.value = e.translationX;
},
onDeactivate: () => {
position.value = withTiming(0, { duration: 100 });
},
});

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

return (
<GestureHandlerRootView style={styles.container}>
<GestureDetector gesture={panGesture}>
<Animated.View style={[styles.box, animatedStyle]} />
</GestureDetector>
</GestureHandlerRootView>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'space-around',
},
box: {
height: 120,
width: 120,
backgroundColor: '#b58df1',
borderRadius: 20,
marginBottom: 30,
},
});
}/>


Multi touch pan handling

If your app relies on multi touch pan handling this section provides some information about how the default behavior differs between platforms and how (if necessary) it can be unified.

The difference in multi touch pan handling lies in the way how translation properties during the event are being calculated.
On iOS the default behavior when more than one finger is placed on the screen is to treat this situation as if only one pointer was placed in the center of mass (average position of all the pointers).
This applies also to many platform native components that handle touch even if not primarily interested in multi touch interactions like for example
UIScrollView component.

On Android, the default behavior for native components like scroll view, pager views or drawers is different and hence gesture defaults to that when it comes to pan handling.
The difference is that instead of treating the center of mass of all the fingers placed as a leading pointer it takes the latest placed finger as such.
This behavior can be changed on Android using
averageTouches flag.

Note that on both Android and iOS when the additional finger is placed on the screen that translation prop is not affected even though the position of the pointer being tracked might have changed.
Therefore it is safe to rely on translation most of the time as it only reflects the movement that happens regardless of how many fingers are placed on the screen and if that number changes over time.
If you wish to track the "center of mass" virtual pointer and account for its changes when the number of fingers changes you can use relative or absolute position provided in the event (
x and y or absoluteX and absoluteY).

Config

<SharedValueInfo />

minDistance

ts
minDistance: number | SharedValue<number>;
text
Minimum distance the finger (or multiple fingers) need to travel before the gesture activates. Expressed in points.

minVelocity

ts
minVelocity: number | SharedValue<number>;
text
Minimum speed the pointer has to reach in order to activate the gesture. Expressed in points per second.

minVelocityX

ts
minVelocityX: number | SharedValue<number>;
text
Minimum speed along X axis the pointer has to reach in order to activate the gesture. Expressed in points per second.

minVelocityY

ts
minVelocityY: number | SharedValue<number>;
text
Minimum speed along Y axis the pointer has to reach in order to activate the gesture. Expressed in points per second.

minPointers

ts
minPointers: number | SharedValue<number>;
text
A number of fingers that is required to be placed before the gesture can activate. Should be higher or equal to 0.

maxPointers

ts
maxPointers: number | SharedValue<number>;
text
When the given number of fingers is placed on the screen and the gesture hasn't yet activated it will fail recognizing the gesture. Should be higher or equal to 0.

activateAfterLongPress

ts
activateAfterLongPress: number | SharedValue<number>;
text
Duration in milliseconds of the LongPress gesture before Pan is allowed to activate. If the finger is moved during that period, the gesture will fail. Should be higher or equal to 0 integer. Default value is 0, meaning no LongPress is required to activate the Pan.

activeOffsetX

ts
activeOffsetX: number |
SharedValue<number> |
[number | SharedValue<number>, number | SharedValue<number>];
text
Range along X axis (in points) where fingers travel without activation of gesture. Moving outside of this range implies activation of gesture. Range can be given as an array or a single number.
If range is set as an array, first value must be lower or equal to 0, and the second one higher or equal to 0.
If only one number
p is given a range of (-inf, p) will be used if p is higher or equal to 0 and (-p, inf) otherwise.

activeOffsetY

ts
activeOffsetY: number |
SharedValue<number> |
[number | SharedValue<number>, number | SharedValue<number>];
text
Range along Y axis (in points) where fingers travel without activation of gesture. Moving outside of this range implies activation of gesture. Range can be given as an array or a single number.
If range is set as an array, first value must be lower or equal to 0, and the second one higher or equal to 0.
If only one number
p is given a range of (-inf, p) will be used if p is higher or equal to 0 and (-p, inf) otherwise.

failOffsetY

ts
failOffsetY: number |
SharedValue<number> |
[number | SharedValue<number>, number | SharedValue<number>];
text
When the finger moves outside this range (in points) along Y axis and the gesture hasn't yet activated it will fail recognizing the gesture. Range can be given as an array or a single number.
If range is set as an array, first value must be lower or equal to 0, and the second one higher or equal to 0.
If only one number
p is given a range of (-inf, p) will be used if p is higher or equal to 0 and (-p, inf) otherwise.

failOffsetX

ts
failOffsetX: number |
SharedValue<number> |
[number | SharedValue<number>, number | SharedValue<number>];
text
When the finger moves outside this range (in points) along X axis and the gesture hasn't yet activated it will fail recognizing the gesture. Range can be given as an array or a single number.
If range is set as an array, first value must be lower or equal to 0, and the second one higher or equal to 0.
If only one number
p is given a range of (-inf, p) will be used if p is higher or equal to 0 and (-p, inf) otherwise.

<Badges platforms={['android']}>

averageTouches [A]


</Badges>
ts
averageTouches: boolean | SharedValue<boolean>;
text
Android, by default, will calculate translation values based on the position of the leading pointer (the first one that was placed on the screen). This modifier allows that behavior to be changed to the one that is default on iOS - the averaged position of all active pointers will be used to calculate the translation values.

<Badges platforms={['ios', 'web']}>

enableTrackpadTwoFingerGesture [I][W]


</Badges>
ts
enableTrackpadTwoFingerGesture: boolean | SharedValue<boolean>;
text
Enables two-finger gestures on supported devices, for example iPads with trackpads. If not enabled the gesture will require click + drag, with enableTrackpadTwoFingerGesture swiping with two fingers will also trigger the gesture.

<MouseButtonProp />

<BaseContinuousGestureConfig />
<BaseGestureConfig />

Callbacks

<BaseGestureCallbacks gesture={"Pan"}/>
<BaseContinuousGestureCallbacks gesture={"Pan"}/>

Event data

translationX

ts
translationX: number;
text
Translation of the pan gesture along X axis accumulated over the time of the gesture. The value is expressed in the point units.

translationY

ts
translationY: number;
text
Translation of the pan gesture along Y axis accumulated over the time of the gesture. The value is expressed in the point units.

changeX

ts
changeX: number;
text
The horizontal distance moved since the last event frame. This value represents the immediate incremental change in the X axis, rather than the total accumulated distance.

changeY

ts
changeY: number;
text
The vertical distance moved since the last event frame. This value represents the immediate incremental change in the Y axis, rather than the total accumulated distance.

velocityX

ts
velocityX: number;
text
Velocity of the pan gesture along the X axis in the current moment. The value is expressed in point units per second.

velocityY

ts
velocityY: number;
text
Velocity of the pan gesture along the Y axis in the current moment. The value is expressed in point units per second.

x

ts
x: number;
text
X coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the GestureDetector. Expressed in point units.

y

ts
y: number;
text
Y coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the GestureDetector. Expressed in point units.

absoluteX

ts
absoluteX: number;
text
X coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the window. The value is expressed in point units. It is recommended to use it instead of x in cases when the original view can be transformed as an effect of the gesture.

absoluteY

ts
absoluteY: number;
text
Y coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the window. The value is expressed in point units. It is recommended to use it instead of y in cases when the original view can be transformed as an effect of the gesture.

stylusData

<CollapsibleCode
label="Show composed types definitions"
expandedLabel="Hide composed types definitions"
lineBounds={[0, 1]}
collapsed={false}
src={

stylusData: StylusData;

interface StylusData {
tiltX: number;
tiltY: number;
azimuthAngle: number;
altitudeAngle: number;
pressure: number;
}
}/>

Object that contains additional information about stylus. It consists of the following fields:

- tiltX - angle in degrees between the Y-Z plane of the stylus and the screen.
-
tiltY - angle in degrees between the X-Z plane of the stylus and the screen.
-
altitudeAngle - angle between stylus axis and the X-Y plane of a device screen.
-
azimuthAngle - angle between the Y-Z plane and the plane containing both the stylus axis and the Y axis.
-
pressure - indicates the normalized pressure of the stylus.

<BaseEventData />

---

Packages/Docs Gesture Handler/Docs/Gestures/Use Pinch Gesture (packages/docs-gesture-handler/docs/gestures/use-pinch-gesture.mdx)

---
id: use-pinch-gesture
title: Pinch gesture
sidebar_label: Pinch gesture
sidebar_position: 5
---

import { webContainer } from '@site/src/utils/getGestureStyles';

import PinchGestureBasic from '@site/static/examples/PinchGestureBasic';
import PinchGestureBasicSrc from '!!raw-loader!@site/static/examples/PinchGestureBasicSrc';

Gesture that recognizes pinching. It allows for tracking the distance between two fingers and uses that information to scale or zoom your content.

<div className={webContainer}>
<InteractiveExample
component={<PinchGestureBasic/>}
src={PinchGestureBasicSrc}
disableMarginBottom={true}
/>
</div>

import BaseEventData from './\_shared/base-gesture-event-data.mdx';
import BaseGestureConfig from './\_shared/base-gesture-config.mdx';
import BaseContinuousGestureConfig from './\_shared/base-continuous-gesture-config.md';
import BaseGestureCallbacks from './\_shared/base-gesture-callbacks.mdx';
import BaseContinuousGestureCallbacks from './\_shared/base-continuous-gesture-callbacks.mdx';
import SharedValueInfo from './\_shared/shared-value-info.md';

The gesture activates when fingers are placed on the screen and move away from each other or pull closer together. It provides information about velocity, anchor (focal) point of gesture and scale.

The distance between the fingers is reported as a scale factor. At the beginning of the gesture, the scale factor is 1.0. As the distance between the two fingers increases, the scale factor increases proportionally.
Similarly, the scale factor decreases as the distance between the fingers decreases.

Pinch gestures are used most commonly to change the size of objects or content onscreen.
For example, map views use pinch gestures to change the zoom level of the map.

:::note
When implementing pinch based on focal point, make sure to use it after the gesture had activated, i.e. in
onActivate or onUpdate callbacks. Using it in onBegin may lead to unexpected behavior.
:::

Example

<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[11, 31]}
src={

import { StyleSheet } from 'react-native';
import {
GestureDetector,
GestureHandlerRootView,
usePinchGesture,
} from 'react-native-gesture-handler';
import Animated, {
useSharedValue,
useAnimatedStyle,
} from 'react-native-reanimated';

export default function App() {
const scale = useSharedValue(1);

const pinchGesture = usePinchGesture({
onUpdate: (e) => {
scale.value *= e.scaleChange;
},
});

const animatedStyle = useAnimatedStyle(() => ({
transform: [{ scale: scale.value }],
}));

return (
<GestureHandlerRootView style={styles.container}>
<GestureDetector gesture={pinchGesture}>
<Animated.View style={[styles.box, animatedStyle]} />
</GestureDetector>
</GestureHandlerRootView>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'space-around',
},
box: {
height: 120,
width: 120,
backgroundColor: '#b58df1',
borderRadius: 20,
marginBottom: 30,
},
});
}/>

Config

<SharedValueInfo />

<BaseContinuousGestureConfig />
<BaseGestureConfig />

Callbacks

<BaseGestureCallbacks gesture={"Pinch"}/>
<BaseContinuousGestureCallbacks gesture={"Pinch"}/>

Event data

scale

ts
scale: number;
text
The scale factor relative to the points of the two touches in screen coordinates.

scaleChange

ts
scaleChange: number;
text
The incremental change in scale since the last event frame. This value represents the ratio of the current scale to the previous scale, rather than the total accumulated scale of the gesture.

velocity

ts
velocity: number;
text
Velocity of the pinch gesture at the current moment. The value is expressed in scale factor per second.

focalX

ts
focalX: number;
text
Position expressed in points along X axis of center anchor point of gesture.

focalY

ts
focalY: number;
text
Position expressed in points along Y axis of center anchor point of gesture.

<BaseEventData />

---

Packages/Docs Gesture Handler/Docs/Gestures/Use Rotation Gesture (packages/docs-gesture-handler/docs/gestures/use-rotation-gesture.mdx)

---
id: use-rotation-gesture
title: Rotation gesture
sidebar_label: Rotation gesture
sidebar_position: 4
---

import { webContainer } from '@site/src/utils/getGestureStyles';

import RotationGestureBasic from '@site/static/examples/RotationGestureBasic';
import RotationGestureBasicSrc from '!!raw-loader!@site/static/examples/RotationGestureBasicSrc';

Gesture that can recognize rotation and track its movement.

<div className={webContainer}>
<InteractiveExample
component={<RotationGestureBasic/>}
src={RotationGestureBasicSrc}
disableMarginBottom={true}
/>
</div>

import BaseEventData from './\_shared/base-gesture-event-data.mdx';
import BaseGestureConfig from './\_shared/base-gesture-config.mdx';
import BaseContinuousGestureConfig from './\_shared/base-continuous-gesture-config.md';
import BaseGestureCallbacks from './\_shared/base-gesture-callbacks.mdx';
import BaseContinuousGestureCallbacks from './\_shared/base-continuous-gesture-callbacks.mdx';
import SharedValueInfo from './\_shared/shared-value-info.md';

import RotationExample from '!!raw-loader!./_examples/RotationExample';


The gesture activates when fingers are placed on the screen and rotate around a common point. It provides information such as the amount rotated, the focal point of the rotation (anchor), and its instantaneous velocity.

:::note
When implementing rotation based on
anchor point, make sure to use it after the gesture had activated, i.e. in onActivate or onUpdate callbacks. Using it in onBegin may lead to unexpected behavior.
:::

Example

<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[11, 31]}
src={RotationExample}/>

Config

<SharedValueInfo />

<BaseContinuousGestureConfig />
<BaseGestureConfig />

Callbacks

<BaseGestureCallbacks gesture={"Rotation"}/>
<BaseContinuousGestureCallbacks gesture={"Rotation"}/>

Event data

rotation

ts
rotation: number;
text
Amount rotated, expressed in radians, from the gesture's focal point (anchor).

rotationChange

ts
rotationChange: number;
text
The incremental change in rotation since the last event frame. This value represents the difference in radians between the current and previous rotation, rather than the total accumulated rotation of the gesture.

velocity

ts
velocity: number;
text
Instantaneous velocity, expressed in point units per second, of the gesture.

anchorX

ts
anchorX: number;
text
X coordinate, expressed in points, of the gesture's central focal point (anchor).

anchorY

ts
anchorY: number;
text
Y coordinate, expressed in points, of the gesture's central focal point (anchor).

<BaseEventData />

---

Packages/Docs Gesture Handler/Docs/Gestures/Use Tap Gesture (packages/docs-gesture-handler/docs/gestures/use-tap-gesture.mdx)

---
id: use-tap-gesture
title: Tap gesture
sidebar_label: Tap gesture
sidebar_position: 2
---

import { vanishOnMobile, appearOnMobile, webContainer } from '@site/src/utils/getGestureStyles';

import useBaseUrl from '@docusaurus/useBaseUrl';

import TapGestureBasic from '@site/static/examples/TapGestureBasic';
import TapGestureBasicSrc from '!!raw-loader!@site/static/examples/TapGestureBasic';

import MouseButtonProp from './\_shared/mouse-button.mdx';

A discrete gesture that recognizes taps.

<div className={webContainer}>
<InteractiveExample
component={<TapGestureBasic/>}
src={TapGestureBasicSrc}
disableMarginBottom={true}
/>
</div>

import BaseEventData from './\_shared/base-gesture-event-data.mdx';
import BaseGestureConfig from './\_shared/base-gesture-config.mdx';
import BaseGestureCallbacks from './\_shared/base-gesture-callbacks.mdx';
import SharedValueInfo from './\_shared/shared-value-info.md';


Tap gestures detect one or more fingers briefly touching the screen.
The pointers involved in these gestures must not move significantly from their initial touch positions. This can be changed via configuration.
The required number of taps and allowed distance from initial position may be configured.
For example, you might configure tap gesture recognizers to detect single taps, double taps, or triple taps.

In order for a gesture to activate, the specified gesture requirements such as minPointers, numberOfTaps, maxDistance, maxDuration, and maxDelay must be met.

Example

<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[7, 21]}
src={

import { View, StyleSheet } from 'react-native';
import {
GestureDetector,
GestureHandlerRootView,
useTapGesture,
} from 'react-native-gesture-handler';

export default function App() {
const singleTap = useTapGesture({
onActivate: () => {
console.log('Single tap!');
},
});

return (
<GestureHandlerRootView style={styles.container}>
<GestureDetector gesture={singleTap}>
<View style={styles.box} />
</GestureDetector>
</GestureHandlerRootView>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'space-around',
},
box: {
height: 120,
width: 120,
backgroundColor: '#b58df1',
borderRadius: 20,
marginBottom: 30,
},
});
}/>

Config

<SharedValueInfo />

minPointers

ts
minPointers: number | SharedValue<number>;
text
Minimum number of pointers (fingers) required to be placed before the gesture activates. Should be a positive integer. The default value is 1.

maxDuration

ts
maxDuration: number | SharedValue<number>;
text
Maximum time, expressed in milliseconds, that defines how fast a finger must be released after a touch. The default value is 500.

maxDelay

ts
maxDelay: number | SharedValue<number>;
text
Maximum time, expressed in milliseconds, that can pass before the next tap — if many taps are required. The default value is 500.

numberOfTaps

ts
numberOfTaps: number | SharedValue<number>;
text
Number of tap gestures required to activate the gesture. The default value is 1.

maxDeltaX

ts
maxDeltaX: number | SharedValue<number>;
text
Maximum distance, expressed in points, that defines how far the finger is allowed to travel along the X axis during a tap gesture. If the finger travels further than the defined distance along the X axis and the gesture hasn't yet activated, it will fail to recognize the gesture.

maxDeltaY

ts
maxDeltaY: number | SharedValue<number>;
text
Maximum distance, expressed in points, that defines how far the finger is allowed to travel along the Y axis during a tap gesture. If the finger travels further than the defined distance along the Y axis and the gesture hasn't yet activated, it will fail to recognize the gesture.

maxDistance

ts
maxDistance: number | SharedValue<number>;
text
Maximum distance, expressed in points, that defines how far the finger is allowed to travel during a tap gesture. If the finger travels further than the defined distance and the gesture hasn't yet activated, it will fail to recognize the gesture.

<MouseButtonProp />

<BaseGestureConfig />

Callbacks

<BaseGestureCallbacks gesture={"Tap"}/>

Event data

x

ts
x: number;
text
X coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the GestureDetector.

y

ts
y: number;
text
Y coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the GestureDetector.

absoluteX

ts
absoluteX: number;
text
X coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the window. It is recommended to use absoluteX instead of x in cases when the view attached to the GestureDetector can be transformed as an effect of the gesture.

absoluteY

ts
absoluteY: number;
text
Y coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the window. It is recommended to use absoluteY instead of y in cases when the view attached to the GestureDetector can be transformed as an effect of the gesture.

<BaseEventData />

---

Packages/Docs Gesture Handler/Docs/Gestures/ Manual Gesture Steps/Step1 (packages/docs-gesture-handler/docs/gestures/_manual_gesture_steps/step1.md)

tsx
type Pointer = {
x: number;
y: number;
visible: boolean;
};
text
---

Packages/Docs Gesture Handler/Docs/Gestures/ Manual Gesture Steps/Step2 (packages/docs-gesture-handler/docs/gestures/_manual_gesture_steps/step2.md)

tsx
import { StyleSheet } from 'react-native';
import Animated, {
useAnimatedStyle,
SharedValue,
} from 'react-native-reanimated';

type Pointer = {
x: number;
y: number;
visible: boolean;
};

type PointerElementProps = {
pointer: SharedValue<Pointer>;
active: SharedValue<boolean>;
};

function PointerElement(props: PointerElementProps) {
const animatedStyle = useAnimatedStyle(() => ({
transform: [
{ translateX: props.pointer.value.x },
{ translateY: props.pointer.value.y },
{
scale:
(props.pointer.value.visible ? 1 : 0) *
(props.active.value ? 1.3 : 1),
},
],
backgroundColor: props.active.value ? 'red' : 'blue',
}));

return <Animated.View style={[styles.pointer, animatedStyle]} />;
}

const styles = StyleSheet.create({
pointer: {
width: 60,
height: 60,
borderRadius: 30,
backgroundColor: 'red',
position: 'absolute',
marginStart: -30,
marginTop: -30,
},
});

text
---

Packages/Docs Gesture Handler/Docs/Gestures/ Manual Gesture Steps/Step3 (packages/docs-gesture-handler/docs/gestures/_manual_gesture_steps/step3.md)

tsx
import { StyleSheet } from 'react-native';
import {
GestureDetector,
GestureHandlerRootView,
GestureStateManager,
useManualGesture,
} from 'react-native-gesture-handler';
import Animated, {
useAnimatedStyle,
SharedValue,
useSharedValue,
} from 'react-native-reanimated';

...

export default function Example() {
const trackedPointers: SharedValue<Pointer>[] = [];
const active = useSharedValue(false);

for (let i = 0; i < 10; i++) {
trackedPointers[i] = useSharedValue<Pointer>({
x: 0,
y: 0,
visible: false,
});
}

const gesture = useManualGesture({});

return (
<GestureHandlerRootView>
<GestureDetector gesture={gesture}>
<Animated.View style={{ flex: 1 }}>
{trackedPointers.map((pointer, index) => (
<PointerElement pointer={pointer} active={active} key={index} />
))}
</Animated.View>
</GestureDetector>
</GestureHandlerRootView>
);
}

const styles = StyleSheet.create({
pointer: {
width: 60,
height: 60,
borderRadius: 30,
backgroundColor: 'red',
position: 'absolute',
marginStart: -30,
marginTop: -30,
},
});

text
---

Packages/Docs Gesture Handler/Docs/Gestures/ Manual Gesture Steps/Step4 (packages/docs-gesture-handler/docs/gestures/_manual_gesture_steps/step4.md)

tsx
const gesture = useManualGesture({
onTouchesDown: (e) => {
for (const touch of e.changedTouches) {
trackedPointers[touch.id].value = {
x: touch.x,
y: touch.y,
visible: true,
};
}

if (e.numberOfTouches >= 2) {
GestureStateManager.activate(e.handlerTag);
}
},
});

text
---

Packages/Docs Gesture Handler/Docs/Gestures/ Manual Gesture Steps/Step5 (packages/docs-gesture-handler/docs/gestures/_manual_gesture_steps/step5.md)

tsx
const gesture = useManualGesture({
...
onTouchesMove: (e) => {
for (const touch of e.changedTouches) {
trackedPointers[touch.id].value = {
x: touch.x,
y: touch.y,
visible: true,
};
}
},
});
text
---

Packages/Docs Gesture Handler/Docs/Gestures/ Manual Gesture Steps/Step6 (packages/docs-gesture-handler/docs/gestures/_manual_gesture_steps/step6.md)

tsx
const gesture = useManualGesture({
...
onTouchesUp: (e) => {
for (const touch of e.changedTouches) {
trackedPointers[touch.id].value = {
x: touch.x,
y: touch.y,
visible: false,
};
}

if (e.numberOfTouches === 0) {
GestureStateManager.deactivate(e.handlerTag);
}
},
});

text
---

Packages/Docs Gesture Handler/Docs/Gestures/ Manual Gesture Steps/Step7 (packages/docs-gesture-handler/docs/gestures/_manual_gesture_steps/step7.md)

tsx
const gesture = useManualGesture({
...
onActivate: () => {
active.value = true;
},
onDeactivate: () => {
active.value = false;
},
text
---

Packages/Docs Gesture Handler/Docs/Gestures/ Shared/Base Continuous Gesture Callbacks (packages/docs-gesture-handler/docs/gestures/_shared/base-continuous-gesture-callbacks.mdx)

import CodeBlock from '@theme/CodeBlock';

onUpdate

{
<CodeBlock className="language-ts">
{
onUpdate: (event: ${props.gesture}HandlerData) => void}
</CodeBlock>
}

Set the callback that is being called every time the gesture receives an update while it's active.

---

Packages/Docs Gesture Handler/Docs/Gestures/ Shared/Base Continuous Gesture Config (packages/docs-gesture-handler/docs/gestures/_shared/base-continuous-gesture-config.md)

manualActivation

ts
manualActivation: boolean | SharedValue<boolean>;
text
When true, the handler will not activate by itself even if its activation criteria are met. Instead, you can manipulate its state using GestureStateManager. Example usage can be found here.

---

Packages/Docs Gesture Handler/Docs/Gestures/ Shared/Base Gesture Callbacks (packages/docs-gesture-handler/docs/gestures/_shared/base-gesture-callbacks.mdx)

import CodeBlock from '@theme/CodeBlock';

onBegin

{
<CodeBlock className="language-ts">
{
onBegin: (event: ${props.gesture}HandlerData) => void}
</CodeBlock>
}

Set the callback that is being called when given gesture handler starts receiving touches. At the moment of this callback the handler is not yet in an active state and we don't know yet if it will recognize the gesture at all.

onActivate

{
<CodeBlock className="language-ts">
{
onActivate: (event: ${props.gesture}HandlerData) => void}
</CodeBlock>
}

Set the callback that is being called when the gesture is recognized by the handler and it transitions to the active state.

onDeactivate

{
<CodeBlock className="language-ts">
{
onDeactivate: (event: ${props.gesture}HandlerData & { canceled: boolean }) => void}
</CodeBlock>
}

Set the callback that is being called when the gesture that was recognized by the handler finishes. It will be called only if the handler was previously in the active state. The event object contains a canceled property — if the gesture was interrupted, canceled is set to true. Otherwise it is set to false.

onFinalize

{
<CodeBlock className="language-ts">
{
onFinalize: (event: ${props.gesture}HandlerData & { canceled: boolean }) => void}
</CodeBlock>
}

Set the callback that is being called when the handler finalizes handling gesture - the gesture was recognized and has finished or it failed to recognize. The event object contains a canceled property — if the gesture failed to activate or was interrupted, canceled is set to true. Otherwise it is set to false.

onTouchesDown

ts
onTouchesDown: (event: GestureTouchEvent) => void
text
Set the onTouchesDown callback which is called every time a finger is placed on the screen.

onTouchesMove

ts
onTouchesMove: (event: GestureTouchEvent) => void
text
Set the onTouchesMove callback which is called every time a finger is moved on the screen.

onTouchesUp

ts
onTouchesUp: (event: GestureTouchEvent) => void
text
Set the onTouchesUp callback which is called every time a finger is lifted from the screen.

onTouchesCancel

ts
onTouchesCancel: (event: GestureTouchEvent) => void
text
Set the onTouchesCancel callback which is called every time a finger stops being tracked, for example when the gesture finishes.

---

Packages/Docs Gesture Handler/Docs/Gestures/ Shared/Base Gesture Config (packages/docs-gesture-handler/docs/gestures/_shared/base-gesture-config.mdx)

enabled

ts
enabled: boolean | SharedValue<boolean>;
text
Indicates whether the given handler should be analyzing the stream of touch events or not.
When set to
false, we can be sure that the handler will never activate.
If the value gets updated while the handler has already started recognizing a gesture, then the handler will stop processing gestures immediately.
Default value is
true.

shouldCancelWhenOutside

ts
shouldCancelWhenOutside: boolean | SharedValue<boolean>;
text
When true, the handler will stop recognition whenever the finger leaves the area of the connected view.
Default value of this property is different depending on the handler type.
Most handlers'
shouldCancelWhenOutside property defaults to false except for the LongPress, Tap and Native (on Android and web), which default to true.

hitSlop

<CollapsibleCode
label="Show composed types definitions"
expandedLabel="Hide composed types definitions"
lineBounds={[0, 1]}
collapsed={false}
src={

hitSlop: HitSlop | SharedValue<HitSlop>;

type HitSlop =
| number
| null
| undefined
| Partial<
Record<
'left' | 'right' | 'top' | 'bottom' | 'vertical' | 'horizontal',
number
>
>
| Record<'width' | 'left', number>
| Record<'width' | 'right', number>
| Record<'height' | 'top', number>
| Record<'height' | 'bottom', number>;
}/>

This parameter enables control over what part of the connected view area can be used to begin recognizing the gesture.
When a negative number is provided, the bounds of the view will reduce the area by the given number of points in each of the sides evenly.

Instead you can pass an object to specify how each boundary side should be reduced by providing different number of points for left, right, top or bottom sides.
You can alternatively provide
horizontal or vertical instead of specifying directly left, right or top and bottom.
Finally, the object can also take
width and height attributes.
When
width is set it is only allowed to specify one of the sides right or left.
Similarly when
height is provided only top or bottom can be set.
Specifying
width or height is useful if we only want the gesture to activate on the edge of the view. In that case for example we can set left: 0 and width: 20 which would make it possible for the gesture to be recognized when started no more than 20 points from the left edge.

IMPORTANT: Note that this parameter is primarily designed to reduce the area where gesture can activate. Hence it is only supported for all the values (except width and height) to be non positive (0 or lower). Although on Android it is supported for the values to also be positive and therefore allow to expand beyond view bounds but not further than the parent view bounds. To achieve this effect on both platforms you can use React Native's View hitSlop property.

testID

ts
testID: string;
text
Sets a testID property for gesture object, allowing for querying for it in tests.

<Badges platforms={['ios']}>

cancelsTouchesInView [I]


</Badges>
ts
cancelsTouchesInView: boolean | SharedValue<boolean>;
text
Accepts a boolean value.
When
true, the gesture will cancel touches for native UI components (UIButton, UISwitch, etc) it's attached to upon activation.
Default value is
true.

runOnJS

Requires react-native-reanimated

ts
runOnJS: boolean | SharedValue<boolean>;
text
If set to true, callbacks will be executed on JS runtime. Can be changed dynamically throughout gesture lifecycle. Defaults to false. For more details, see the runOnJS section.

disableReanimated

Requires react-native-reanimated

ts
disableReanimated: boolean;
text
If set to true, the gesture will ignore any interaction with Reanimated. This property cannot be changed during the gesture's lifecycle. For more details, see the disableReanimated section.

simultaneousWith

ts
simultaneousWith: Gesture | Gesture[]
text
Adds a gesture that should be recognized simultaneously with this one.

IMPORTANT: Note that this method only marks the relation between gestures, without composing them. GestureDetector will not recognize other gestures and it needs to be added to another detector in order to be recognized.

requireToFail

ts
requireToFail: Gesture | Gesture[]
text
Adds a relation requiring another gesture to fail, before this one can activate.

IMPORTANT: Note that this method only marks the relation between gestures, without composing them. GestureDetector will not recognize other gestures and it needs to be added to another detector in order to be recognized.

block

ts
block: Gesture | Gesture[]
text
Adds a relation that makes other gestures wait with activation until this gesture fails (or doesn't start at all).

IMPORTANT: Note that this method only marks the relation between gestures, without composing them. GestureDetector will not recognize other gestures and it needs to be added to another detector in order to be recognized.

useAnimated

ts
useAnimated: boolean;
text
Setting this property to true ensures that the Animated API functions correctly when useNativeDriver is set to false. The default value is set to false.

<Badges platforms={['web']}>

activeCursor [W]


</Badges>
ts
activeCursor: ActiveCursor | SharedValue<ActiveCursor>;
text
This parameter allows specifying which cursor should be used when the gesture activates. Supports all CSS cursor values (e.g. "grab", "zoom-in"). Default value is set to "auto".

<Badges platforms={['ios', 'android']}>
### cancelsJSResponder [A][I]
</Badges>

ts
cancelsJSResponder?: boolean;
text
Controls whether activating a Gesture Handler recognizer should cancel React Native JS responders.

Setting this property to true ensures that the gesture handler cancels any active React Native JS responders in the same root view upon activation. When set to false, both Gesture Handler and React Native responder callbacks can run simultaneously. The default value is set to true.

---

Packages/Docs Gesture Handler/Docs/Gestures/ Shared/Base Gesture Event Data (packages/docs-gesture-handler/docs/gestures/_shared/base-gesture-event-data.mdx)

numberOfPointers

ts
numberOfPointers: number;
text
Represents the number of pointers (fingers) currently placed on the screen.

pointerType

<CollapsibleCode
label="Show composed types definitions"
expandedLabel="Hide composed types definitions"
lineBounds={[0, 1]}
collapsed={false}
src={

pointerType: PointerType;

enum PointerType {
TOUCH,
STYLUS,
MOUSE,
KEY,
OTHER,
}
}/>

Indicates the type of pointer device in use.

---

Packages/Docs Gesture Handler/Docs/Gestures/ Shared/Mouse Button (packages/docs-gesture-handler/docs/gestures/_shared/mouse-button.mdx)

<Badges platforms={['android', 'web']}>

mouseButton [A][W]


</Badges>

<CollapsibleCode
label="Show composed types definitions"
expandedLabel="Hide composed types definitions"
lineBounds={[0, 1]}
collapsed={false}
src={

mouseButton: MouseButton | SharedValue<MouseButton>;

enum MouseButton {
LEFT,
RIGHT,
MIDDLE,
BUTTON_4,
BUTTON_5,
ALL,
}
}/>

Allows users to choose which mouse buttons to respond to. Arguments can be combined using | operator, e.g. mouseButton(MouseButton.LEFT | MouseButton.RIGHT). Default value is set to MouseButton.LEFT.

---

Packages/Docs Gesture Handler/Docs/Gestures/ Shared/Shared Value Info (packages/docs-gesture-handler/docs/gestures/_shared/shared-value-info.md)

Properties supporting SharedValue can be updated dynamically via Reanimated Shared Values without triggering a re-render.

---

Packages/Docs Gesture Handler/Docs/Guides/ Category .Json (packages/docs-gesture-handler/docs/guides/_category_.json)

{
"label": "Guides",
"position": 2,
"link": {
"type": "generated-index"
}
}

---

Packages/Docs Gesture Handler/Docs/Guides/Swipe And Scroll (packages/docs-gesture-handler/docs/guides/swipe-and-scroll.md)

---
id: swipe-and-scroll
title: Custom swipeable components inside ScrollView (web)
sidebar_position: 4
---

While we recommend using our own ReanimatedSwipeable component, creating your own version of swipeable gives you more control over its behavior. A common issue here is that after creating your own swipeable component, scroll does not work. In that case, try adding touchAction set to "pan-y", like this:

jsx
<GestureDetector gesture={...} ... touchAction="pan-y">
...
</GestureDetector>
text
---

Packages/Docs Gesture Handler/Docs/Guides/Testing (packages/docs-gesture-handler/docs/guides/testing.mdx)

---
id: testing
title: Testing with Jest
sidebar_position: 3
---

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

Setup

Jest configuration

In order to use functions provided by Gesture Handler, add react-native-gesture-handler to transformIgnorePatterns in jest config.

js
transformIgnorePatterns: [
'node_modules/(?!((jest-)?react-native|react-native-gesture-handler)/)',
],
text
:::note
Be careful when adding multiple entries to
transformIgnorePatterns. Since Jest ignores a file if it matches any pattern in the list, splitting negative lookaheads into separate strings often causes them to override each other. It is safer to combine your exceptions into a single regex using the | operator. See the Jest documentation for an example.
:::

Mocking native modules

In order to load mocks provided by RNGH, add the following to your jest config:

json
"setupFiles": ["./node_modules/react-native-gesture-handler/jestSetup.js"]
text

Example jest config

<Tabs groupId="package-managers">
<TabItem value="js" label="jest.config.js" default>

js
module.exports = {
preset: '@react-native/jest-preset',
transformIgnorePatterns: [
'node_modules/(?!((jest-)?react-native|react-native-gesture-handler)/)',
],
setupFiles: ['react-native-gesture-handler/jestSetup.js'],
};
text
</TabItem>
<TabItem value="json" label="package.json">
json
"jest": {
"preset": "@react-native/jest-preset",
"transformIgnorePatterns": [
"node_modules/(?!((jest-)?react-native|react-native-gesture-handler)/)"
],
"setupFiles": [
"react-native-gesture-handler/jestSetup.js"
]
}
text
</TabItem>
</Tabs>

Testing Gestures' and Gesture handlers' callbacks

RNGH provides the following APIs for triggering selected handlers:

- createGestureController to imperatively control a gesture lifecycle one step at a time.
-
fireGestureHandler to dispatch a complete event stream.
-
getByGestureTestId to find a gesture by its test ID.

createGestureController

<CollapsibleCode
label="Show composed types definitions"
expandedLabel="Hide composed types definitions"
lineBounds={[0, 3]}
src={

import { createGestureController } from 'react-native-gesture-handler/jest-utils';

createGestureController: (componentOrGesture) => GestureController;

export interface GestureController<
TEventPayload extends Record<string, unknown> = Record<string, unknown>,

{

begin: (event?: GestureControllerEvent<TEventPayload>) => void;
activate: (event?: GestureControllerEvent<TEventPayload>) => void;
update: (event?: GestureControllerEvent<TEventPayload>) => void;
end: (event?: GestureControllerEvent<TEventPayload>) => void;
fail: (event?: GestureControllerEvent<TEventPayload>) => void;
cancel: (event?: GestureControllerEvent<TEventPayload>) => void;
}

export type GestureControllerEvent<
TEventPayload extends Record<string, unknown> = Record<string, unknown>,

= Partial<TEventPayload> & {

handlerTag?: never;
nativeEvent?: never;
oldState?: never;
state?: never;
};
}/>

Creates an imperative controller that dispatches gesture lifecycle events one step
at a time. This allows the test to assert application state between lifecycle steps
without manually supplying
state, oldState, or handlerTag.

componentOrGesture can be:

- A Gesture Handler component found using a Jest query such as getByTestId.
- A gesture object.
- A gesture test ID string.

When a gesture object is passed directly, the event payload type is inferred from
the gesture. Every lifecycle method accepts an optional partial event payload and
fills omitted handler-specific properties with defaults.

The controller exposes the following methods:

| Method | Behavior |
| ------------ | ---------------------------------------------------------------------------------------------------------------- |
|
begin() | Starts a stream and calls onBegin. If the previous stream finished, the controller resets it before beginning. |
|
activate() | Activates a begun stream and calls onActivate. |
|
update() | Dispatches an update for an active stream and calls onUpdate. It can be called multiple times. |
|
end() | Ends a begun or active stream. Calls onDeactivate if active, then onFinalize with canceled: false. |
|
fail() | Fails a begun or active stream. Calls onDeactivate if active, then onFinalize with canceled: true. |
|
cancel() | Cancels a begun or active stream. Calls onDeactivate if active, then onFinalize with canceled: true. |

Calling begin() again after end(), fail(), or cancel() starts another stream with the same controller.
Calling methods in an invalid order throws an error. State-machine fields such as
state, oldState, handlerTag, and nativeEvent cannot be supplied in event
payloads because the controller manages them internally.

tsx
test('updates application state after each gesture step', () => {
const onBegin = jest.fn();
const onActivate = jest.fn();
const onUpdate = jest.fn();
const onDeactivate = jest.fn();
const onFinalize = jest.fn();

const panGesture = renderHook(() =>
usePanGesture({
disableReanimated: true,
onBegin,
onActivate,
onUpdate,
onDeactivate,
onFinalize,
})
).result.current;

const controller = createGestureController(panGesture);

controller.begin();
expect(onBegin).toHaveBeenCalledTimes(1);

controller.activate();
expect(onActivate).toHaveBeenCalledTimes(1);

controller.update({ translationX: 50 });
expect(onUpdate).toHaveBeenCalledWith(
expect.objectContaining({ translationX: 50 })
);

controller.end();
expect(onDeactivate).toHaveBeenCalledTimes(1);
expect(onFinalize).toHaveBeenCalledWith(
expect.objectContaining({ canceled: false })
);
});

text
:::note
createGestureController controls lifecycle events directly. It does not generate
pointer input, run platform gesture recognizers, or evaluate relations between
gestures.
:::

fireGestureHandler

ts
import { fireGestureHandler } from 'react-native-gesture-handler/jest-utils';

fireGestureHandler: (componentOrGesture, eventList) => void;

text
Simulates one event stream (i.e. event sequence starting with BEGIN state and ending
with one of
END/FAIL/CANCEL states), calling appropriate callbacks associated with given gesture handler.

- componentOrGesture - Either Gesture Handler component found by Jest queries (e.g. getByTestId) or Gesture found by getByGestureTestId()

- eventList - Event data passed to appropriate callback. RNGH fills event list if required
data is missing using these rules:
-
oldState is filled using state of the previous event. BEGIN events use
UNDETERMINED value as previous event.
- Events after first
ACTIVE state can omit state field.
- Handler specific data is filled (e.g.
numberOfTouches, x fields) with
defaults.
- Missing
BEGIN and END events are added with data copied from first and last
passed event, respectively.
- If first event doesn't have
state field, the ACTIVE state is assumed.

Some eventList examples:

jsx
const oldStateFilled = [
{ state: State.BEGAN },
{ state: State.ACTIVE },
{ state: State.END },
]; // three events with specified state are fired.

const implicitActiveState = [
{ state: State.BEGAN },
{ state: State.ACTIVE },
{ x: 5 },
{ state: State.END },
]; // 4 events, including two ACTIVE events (second one has overridden additional data).

const implicitBegin = [
{ x: 1, y: 11 },
{ x: 2, y: 12, state: State.FAILED },
]; // 3 events, including implicit BEGAN, one ACTIVE, and one FAILED event with additional data.

const implicitBeginAndEnd = [
{ x: 5, y: 15 },
{ x: 6, y: 16 },
{ x: 7, y: 17 },
]; // 5 events, including 3 ACTIVE events and implicit BEGAN and END events. BEGAN uses first event's additional data, END uses last event's additional data.

const allImplicits = []; // 3 events, one BEGIN, one ACTIVE, one END with defaults.

text

getByGestureTestId

tsx
import { getByGestureTestId } from 'react-native-gesture-handler/jest-utils';

getByGestureTestId: (testID: string) => Gesture;

text
Returns opaque data type associated with gesture. Gesture is found via testID attribute in rendered
components.

:::warning
testID must be unique among components rendered in test.
:::

fireGestureHandler example

Extracted from RNGH tests, check api_v3.test.tsx for full implementation.

tsx
test('Pan gesture', () => {
const onBegin = jest.fn();
const onStart = jest.fn();

const panGesture = renderHook(() =>
usePanGesture({
disableReanimated: true,
onBegin: (e) => onBegin(e),
onActivate: (e) => onStart(e),
})
).result.current;

fireGestureHandler(panGesture, [
{ oldState: State.UNDETERMINED, state: State.BEGAN },
{ oldState: State.BEGAN, state: State.ACTIVE },
{ oldState: State.ACTIVE, state: State.ACTIVE },
{ oldState: State.ACTIVE, state: State.END },
]);

expect(onBegin).toHaveBeenCalledTimes(1);
expect(onStart).toHaveBeenCalledTimes(1);
});

text
---

Packages/Docs Gesture Handler/Docs/Guides/Transformations (packages/docs-gesture-handler/docs/guides/transformations.mdx)

---
id: transformations
title: Transforming a view with multiple gestures
sidebar_label: Transforming a view
sidebar_position: 5
---

Combining Pan, Pinch and Rotation on a single view — the "photo viewer" interaction where you can drag, zoom and twist an image at the same time — is a common use case. Gesture Handler doesn't ship a dedicated component for it, but you can build it yourself on top of the existing gestures. There are two ideas that make it work; the rest is wiring.

This guide assumes you're using Reanimated to run the transformation on the UI thread. The full example is at the end of this page, and a version of it can be found in the example app.

Use a matrix, not separate transforms

Store the whole transformation as a single affine matrix. It bundles translation, scale and rotation into one value that you compose by multiplying matrices together in whatever order you need, and since the result is itself a matrix you can store it and let each new gesture build on top. That gives you a natural split: the accumulated transform lives in the matrix, while the in-progress gesture stays as the plain scale, rotation and translation the callbacks hand you as the fingers move. Each frame, combine the two to draw the view. When the gesture ends, fold its values into the matrix and reset them, so the next gesture picks up where it left off. You don't need to follow the underlying math — the full example has the handful of small helper functions that implement it.

Why not keep separate scale, rotation and translation values and pass them to a transform array instead? That works for a single gesture, but falls apart once you want each one to build on the last. The array does let you control the order its entries apply in, but there's no clean place to accumulate the running result: to preserve what previous gestures did, you'd have to keep appending an entry for every incremental translation, scale and rotation, and React would rebuild the whole matrix from that ever-growing list on each render.

Keep the origin stable

Scaling and rotation pivot around the origin, but the user expects them to pivot around the point between their fingers. To move the pivot, wrap the transform between two translations — shift that point to the origin, apply the scale or rotation, then shift it back. Do this while building the matrix, once for the scale and once for the rotation:

ts
matrix = multiply(matrix, translate(origin.x, origin.y));
matrix = multiply(matrix, scale(scaleValue, scaleValue));
matrix = multiply(matrix, translate(-origin.x, -origin.y));
text
Capture that pivot once, when the gesture activates, and keep it in a shared value:
jsx
const pinch = usePinchGesture({
onActivate: (e) => {
origin.value = {
x: -(e.focalX - size.width / 2),
y: -(e.focalY - size.height / 2),
};
},
// ...
});
text
Set it only on activation and leave it untouched until the gesture ends, so the pivot stays stable — recomputing it every frame, or letting a second simultaneous gesture overwrite it, makes the view jump. The focal point arrives relative to the view rather than its center, which is what the size-based adjustment handles (read the size with onLayout). And when Pinch and Rotation run together, let only the first one set the pivot.

Full example

Put both ideas together and you have the whole interaction. Each gesture updates its own shared value as the fingers move and folds the result into the stored matrix when it ends; useSimultaneousGestures runs Pan, Pinch, Rotation and a double-tap zoom at the same time.

<CollapsibleCode
label="Show full example"
expandedLabel="Hide full example"
lineBounds={[123, 264]}
src={

import React, { useState } from 'react';
import { StyleSheet, View } from 'react-native';
import {
GestureDetector,
usePanGesture,
usePinchGesture,
useRotationGesture,
useSimultaneousGestures,
useTapGesture,
} from 'react-native-gesture-handler';
import Animated, {
useAnimatedStyle,
useSharedValue,
} from 'react-native-reanimated';

function identity3() {
'worklet';
return [1, 0, 0, 0, 1, 0, 0, 0, 1];
}

function multiply3(a: number[], b: number[]) {
'worklet';
return [
a[0] b[0] + a[1] b[3] + a[2] * b[6],
a[0] b[1] + a[1] b[4] + a[2] * b[7],
a[0] b[2] + a[1] b[5] + a[2] * b[8],
a[3] b[0] + a[4] b[3] + a[5] * b[6],
a[3] b[1] + a[4] b[4] + a[5] * b[7],
a[3] b[2] + a[4] b[5] + a[5] * b[8],
a[6] b[0] + a[7] b[3] + a[8] * b[6],
a[6] b[1] + a[7] b[4] + a[8] * b[7],
a[6] b[2] + a[7] b[5] + a[8] * b[8],
];
}

function scale3(sx: number, sy: number) {
'worklet';
return [sx, 0, 0, 0, sy, 0, 0, 0, 1];
}

function translate3(tx: number, ty: number) {
'worklet';
return [1, 0, 0, 0, 1, 0, tx, ty, 1];
}

function rotate3(rad: number) {
'worklet';
const c = Math.cos(rad);
const s = Math.sin(rad);
return [c, -s, 0, s, c, 0, 0, 0, 1];
}

function invert2(m: number[]) {
'worklet';
const a = m[0];
const b = m[1];
const c = m[2];
const d = m[3];
const det = a d - b c;

if (Math.abs(det) < 1e-6) {
return [1, 0, 0, 1];
}

return [d / det, -b / det, -c / det, a / det];
}

function toTransformedCoords(
point: { x: number; y: number },
matrix: number[]
) {
'worklet';
const m2 = [matrix[0], matrix[1], matrix[3], matrix[4]];
const inv = invert2(m2);
const x = point.x;
const y = point.y;
const newX = inv[0] x + inv[2] y;
const newY = inv[1] x + inv[3] y;

return { x: newX, y: newY };
}

function createMatrix(
translation: { x: number; y: number },
scale: number,
rotation: number,
origin: { x: number; y: number }
) {
'worklet';
let matrix = identity3();

if (scale !== 1) {
matrix = multiply3(matrix, translate3(origin.x, origin.y));
matrix = multiply3(matrix, scale3(scale, scale));
matrix = multiply3(matrix, translate3(-origin.x, -origin.y));
}
if (rotation !== 0) {
matrix = multiply3(matrix, translate3(origin.x, origin.y));
matrix = multiply3(matrix, rotate3(-rotation));
matrix = multiply3(matrix, translate3(-origin.x, -origin.y));
}

if (translation.x !== 0 || translation.y !== 0) {
matrix = multiply3(matrix, translate3(translation.x, translation.y));
}

return matrix;
}

function applyTransformations(
translation: { x: number; y: number },
scale: number,
rotation: number,
origin: { x: number; y: number },
matrix: number[]
) {
'worklet';
const translationInViewCoords = toTransformedCoords(translation, matrix);
const transform = createMatrix(
translationInViewCoords,
scale,
rotation,
origin
);
return multiply3(transform, matrix);
}

function Photo() {
const [size, setSize] = useState({ width: 0, height: 0 });
const translation = useSharedValue({ x: 0, y: 0 });
const origin = useSharedValue({ x: 0, y: 0 });
const scale = useSharedValue(1);
const rotation = useSharedValue(0);
const isRotating = useSharedValue(false);
const isScaling = useSharedValue(false);

const transform = useSharedValue(identity3());

const style = useAnimatedStyle(() => {
const matrix = applyTransformations(
translation.value,
scale.value,
rotation.value,
origin.value,
transform.value
);

return {
transform: [
{ translateX: matrix[6] },
{ translateY: matrix[7] },
{ scale: Math.hypot(matrix[0], matrix[1]) },
{ rotateZ: \\${Math.atan2(matrix[1], matrix[0])}rad\ },
],
};
});

const rotationGesture = useRotationGesture({
onActivate: (e) => {
if (!isRotating.value && !isScaling.value) {
origin.value = {
x: -(e.anchorX - size.width / 2),
y: -(e.anchorY - size.height / 2),
};
}
isRotating.value = true;
},
onUpdate: (e) => {
rotation.value += e.rotationChange;
},
onDeactivate: () => {
transform.value = applyTransformations(
translation.value,
scale.value,
rotation.value,
origin.value,
transform.value
);

rotation.value = 0;
translation.value = { x: 0, y: 0 };
scale.value = 1;
isRotating.value = false;
},
});

const scaleGesture = usePinchGesture({
onActivate: (e) => {
if (!isRotating.value && !isScaling.value) {
origin.value = {
x: -(e.focalX - size.width / 2),
y: -(e.focalY - size.height / 2),
};
}
isScaling.value = true;
},
onUpdate: (e) => {
scale.value *= e.scaleChange;
},
onDeactivate: () => {
transform.value = applyTransformations(
translation.value,
scale.value,
rotation.value,
origin.value,
transform.value
);
rotation.value = 0;
translation.value = { x: 0, y: 0 };
scale.value = 1;
isScaling.value = false;
},
});

const panGesture = usePanGesture({
averageTouches: true,
onUpdate: (e) => {
translation.value = {
x: translation.value.x + e.changeX,
y: translation.value.y + e.changeY,
};
},
onDeactivate: () => {
transform.value = applyTransformations(
translation.value,
scale.value,
rotation.value,
origin.value,
transform.value
);

rotation.value = 0;
translation.value = { x: 0, y: 0 };
scale.value = 1;
},
});

const doubleTapGesture = useTapGesture({
numberOfTaps: 2,
onDeactivate: () => {
scale.value *= 1.25;
},
});

const gesture = useSimultaneousGestures(
rotationGesture,
scaleGesture,
panGesture,
doubleTapGesture
);

return (
<GestureDetector gesture={gesture}>
<Animated.View
onLayout={({ nativeEvent }) => {
setSize({
width: nativeEvent.layout.width,
height: nativeEvent.layout.height,
});
}}
style={[styles.container, style]}
/>
</GestureDetector>
);
}

export default function Example() {
return (
<View style={styles.home}>
<Photo />
</View>
);
}

const styles = StyleSheet.create({
home: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
container: {
width: 240,
height: 240,
backgroundColor: '#5b6ef5',
elevation: 8,
borderRadius: 48,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.3,
shadowRadius: 4,
},
});
}/>

---

Packages/Docs Gesture Handler/Docs/Guides/Troubleshooting (packages/docs-gesture-handler/docs/guides/troubleshooting.mdx)

---
id: troubleshooting
title: Troubleshooting
sidebar_position: 2
---

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

Troubleshooting

Thanks for giving this library a try! We are sorry that you might have encountered issues though. Here is how you can seek help:

1. Search over the issues on GitHub. There is a chance someone had this problem in the past and it has been resolved!
2. When sure your problem hasn't been reported or was reported but the proposed solution doesn't work for you please follow our issue reporting guidelines.
3. You can try seeking help on Software Mansion Discord.
4. If you feel like reading the source code we highly recommend it, as this is by far the best resource and gives you the most up to date insights into how the library works and what might be causing the bug.
5. If you managed to find the solution consider contributing a fix or update our documentation to make this information easier to find for others in the future.

Reporting issues

This library is maintained by a very small team. Please keep this in mind when reporting issues, as we may not be able to respond as quickly as you might expect. We strive to address all problems as soon as possible, but our time is often constrained by other issues, features, or projects. To help us understand and address your issue more promptly, you can assist by:

- Making sure the issue description is complete. Please include all the details about your environment (library version, RN version, device OS, etc.).
- It is best to provide an example app that reproduces the issue you are having. Put it up on gist, snack or create a repo on GitHub - it doesn't matter as long as we can easily pull it in, run and see the issue.
- Explain how you run your repro app and what steps to take to reproduce the issue.
- Isolate your issue from other dependencies you might be using and make the repro app as minimal as possible.
- If you have spent some time figuring out the root cause of the problem you can leave a note about your findings so far.
- Do not comment on closed issues. It is very unlikely that we are going to notice your comment in such a case. If the issue has been closed, but the proposed solution doesn't work for you, please open a new one providing all the information necessary and linking to the solution you have tried.

It's not a bug, it's a feature

- Changing enabled prop during a gesture has no effect, only when a gesture starts (that is, a finger touches the screen) the enabled prop is taken into consideration to decide whether to extract (or not) the gesture and provide it with stream of events to analyze.
-
Native gesture may not conform to the standard state flow due to platform specific workarounds to incorporate native views into RNGH.
- Keep in mind that Legacy
Touchables from RNGH are rendering two additional views that may need to be styled separately to achieve desired effect (style and containerStyle props).
- In order for the gesture composition to work, all composed gestures must be attached to the same
GestureHandlerRootView.

Multiple instances of Gesture Handler were detected

This error usually happens when in your project there exists more than one instance of Gesture Handler. It can occur when some of your dependencies have installed Gesture Handler inside their own node_modules instead of using it as a peer dependency. In this case, two different versions of the Gesture Handler JS module try to install the same Native Module.

You can resolve this problem manually by modifying your package.json file. This will depend on the package manager you are using.


<Tabs groupId="package-managers">
<TabItem value="npm" label="NPM">
You can check which libraries are using Gesture Handler, along with its version, with the command:

bash
npm ls react-native-gesture-handler
text
Then you can decide which version of Gesture Handler to use by adding the overrides property.
json
"overrides": {
"react-native-gesture-handler": <Gesture Handler version>
}
text
After that you need to run your package manager again:
bash
npm install
text
</TabItem>
<TabItem value="yarn" label="YARN">
You can check which libraries are using Gesture Handler, along with its version, with the command:
bash
yarn why react-native-gesture-handler
text
Then you can decide which version of Gesture Handler to use by adding the resolutions property.
json
"resolutions": {
"react-native-gesture-handler": <Gesture Handler version>
}
text
After that you need to run your package manager again:
bash
yarn
text
</TabItem>
</Tabs>

RNGestureHandlerModule could not be found

If you see an error TurboModuleRegistry.getEnforcing(...): 'RNGestureHandlerModule' could not be found, this usually happens when react-native-gesture-handler isn't a direct dependency of your app. If it's only pulled in transitively — for example as a peerDependency of some other library you use — React Native's codegen won't run for it, so the native module is never generated, even though the package is present in node_modules.

The fix is to add react-native-gesture-handler directly to your app's own dependencies:

<Tabs groupId="package-managers">
<TabItem value="npm" label="NPM">

bash
npm install react-native-gesture-handler
text
</TabItem>
<TabItem value="yarn" label="YARN">
bash
yarn add react-native-gesture-handler
text
</TabItem>
</Tabs>

If the issue persists after that, please open a new issue following our issue reporting guidelines and attach a minimal, reproducible example.

---

Packages/Docs Gesture Handler/Docs/Guides/Upgrading To 3 (packages/docs-gesture-handler/docs/guides/upgrading-to-3.mdx)

---
id: upgrading-to-3
title: Upgrading to the new API introduced in Gesture Handler 3
sidebar_position: 1
---

:::tip
To make the migration process easier, we have prepared a SKILL that will help your AI assistant to automatically migrate your codebase to the new API.
:::

Migrating gestures

import CodeComparison from '@site/src/components/CodeComparison';

The most important change brought by Gesture Handler 3 is the new hook API. Migration is pretty straightforward. Instead of calling builder methods, everything is passed as a configuration object.

<CodeComparison
label1={"RNGH 2"}
label2={"RNGH 3"}
code1={
const gesture = Gesture.Pan()
.onBegin(() => {
console.log('Pan!');
})
.minDistance(25);
}
code2={
const gesture = usePanGesture({
onBegin: () => {
console.log('Pan!');
},
minDistance: 25,
});
}
/>

ForceTouch gesture is not available in hook API.

<details>
<summary>Full changes</summary>

| RNGH2 | RNGH3 |
| ---------------------- | ----------------------- |
|
Gesture.Pan() | usePanGesture() |
|
Gesture.Tap() | useTapGesture() |
|
Gesture.LongPress() | useLongPressGesture() |
|
Gesture.Rotation() | useRotationGesture() |
|
Gesture.Pinch() | usePinchGesture() |
|
Gesture.Fling() | useFlingGesture() |
|
Gesture.Hover() | useHoverGesture() |
|
Gesture.Native() | useNativeGesture() |
|
Gesture.Manual() | useManualGesture() |
|
Gesture.ForceTouch() | Not available in hook API |

</details>

Renamed callbacks

In Gesture Handler 3 some of the callbacks were renamed, namely:

| RNGH2 | RNGH3 |
| --------- | -------------- |
|
onStart | onActivate |
|
onEnd | onDeactivate |
|
onTouchesCancelled | onTouchesCancel |

Here is a comparison of the two APIs:

<CodeComparison
label1={"RNGH 2"}
label2={"RNGH 3"}
code1={
const gesture = Gesture.Pan()
.onStart(() => {
console.log('Pan started!');
})
.onEnd(() => {
console.log('Pan ended!');
})
.onTouchesCancelled(() => {
console.log('Pan touches cancelled!');
});
}
code2={
const gesture = usePanGesture({
onActivate: () => {
console.log('Pan activated!');
},
onDeactivate: () => {
console.log('Pan deactivated!');
},
onTouchesCancel: () => {
console.log('Pan touches cancelled!');
},
});
}
/>

canceled instead of success

In RNGH2, onEnd and onFinalize received a second success boolean parameter. In RNGH3, this has been replaced with a canceled property on the event object itself. Note that the logic is inverted — canceled: true corresponds to the old success: false.

<CodeComparison
label1={"RNGH 2"}
label2={"RNGH 3"}
code1={
const gesture = Gesture.Tap()
.onEnd((event, success) => {
if (success) {
console.log('Gesture succeeded!');
}
});
}
code2={
const gesture = useTapGesture({
onDeactivate: (event) => {
if (!event.canceled) {
console.log('Gesture succeeded!');
}
},
});
}
/>

onChange

onChange callback has been removed, and its functionality has been integrated into onUpdate. You can now access change* properties in onUpdate callback.

<CodeComparison
label1={"RNGH 2"}
label2={"RNGH 3"}
code1={
const pan = Gesture.Pan().onChange((e) => {
console.log(e.changeX);
});
}
code2={
const pan = usePanGesture({
onUpdate: (e) => {
console.log(e.changeX);
},
});
}
/>

state & oldState

The state and oldState properties are no longer available in event objects. Tracking state changes can now only be accomplished using the appropriate callbacks.

Event types

The types of events have been unified for all callbacks. Each event falls into one of two categories: GestureEvent for gesture callbacks, or GestureTouchEvent for TouchEvent callbacks.

StateManager

In Gesture Handler 3, stateManager is no longer passed to TouchEvent callbacks. Instead, you should use the global GestureStateManager.

<CodeComparison
label1={"RNGH 2"}
label2={"RNGH 3"}
code1={
const manual = Gesture.Manual().onTouchesDown((e, stateManager ) => {
stateManager.activate();
});
}
code2={
const manual = useManualGesture({
onTouchesDown: (e) => {
GestureStateManager.activate(e.handlerTag);
},
});
}
/>

Migrating relations

Composed gestures

Previously, composed gestures were created using Gesture object. In Gesture Handler 3, relations are set up using relation hooks.

<CodeComparison
label1={"RNGH 2"}
label2={"RNGH 3"}
code1={
const pan1 = Gesture.Pan();
const pan2 = Gesture.Pan();

const gesture = Gesture.Simultaneous(pan1, pan2);
}
code2={
const pan1 = usePanGesture({});
const pan2 = usePanGesture({});

const gesture = useSimultaneousGestures(pan1, pan2);
}
/>

Full changes are as follows:

| RNGH2 | RNGH3 |
| ----------------------------------------- | --------------------------- |
|
Gesture.Race() | useCompetingGestures() |
|
Gesture.Simultaneous() | useSimultaneousGestures() |
|
Gesture.Exclusive() | useExclusiveGestures() |

Cross components relations properties

Properties used to define cross-components interactions were renamed.

<CodeComparison
label1={"RNGH 2"}
label2={"RNGH 3"}
code1={
const pan1 = Gesture.Pan();
const pan2 =
Gesture.Pan().requireExternalGestureToFail(pan1);
}
code2={
const pan1 = usePanGesture({});
const pan2 = usePanGesture({
requireToFail: pan1,
});
}
/>

Full changes are as follows:


| RNGH2 | RNGH3 |
| ----------------------------------------- | --------------------------- |
|
gesture.simultaneousWithExternalGesture | gesture.simultaneousWith |
|
gesture.requireExternalGestureToFail | gesture.requireToFail |
|
gesture.blocksExternalGesture | gesture.block |


Migrating components relying on view hierarchy

Certain components, such as SVG, depend on the view hierarchy to function correctly. In Gesture Handler 3, GestureDetector disrupts these hierarchies. To resolve this issue, two new detectors have been introduced: InterceptingGestureDetector and VirtualGestureDetector.

:::danger Detectors order
VirtualGestureDetector has to be a descendant of InterceptingGestureDetector.
:::

Migrating SVG

In Gesture Handler 2, it was possible to use GestureDetector directly on SVG. In Gesture Handler 3, the correct way to interact with SVG is to use InterceptingGestureDetector and VirtualGestureDetector.

<CodeComparison
label1={"RNGH 2"}
label2={"RNGH 3"}
code1={

<GestureDetector gesture={containerTap}>
<Svg>
<GestureDetector gesture={circleTap}>
<Circle />
</GestureDetector>
</Svg>
</GestureDetector>
}
code2={

<InterceptingGestureDetector gesture={containerTap}>
<Svg>
<VirtualGestureDetector gesture={circleTap}>
<Circle />
</VirtualGestureDetector>
</Svg>
</InterceptingGestureDetector>
}
/>

Old components

Buttons

RNGH3 introduces the Touchable component — a flexible, unified replacement for all previous button types. While Touchable shares most of the logic with our standard buttons, it offers a more customizable API.

To help you migrate, here is the current state of our button components:

- Touchable - The recommended component. Can be used to replicate both RectButton and BorderlessButton effects by adjusting the underlayColor, activeOpacity, and animationDuration props.
- To replace
RectButton, add underlayColor="black" and animationDuration={0} (the legacy button switches states instantly).
- To replace
BorderlessButton, add activeOpacity={0.3} and animationDuration={0}.
- Android ripple: legacy
RectButton/BorderlessButton show the native theme ripple on Android by default, while Touchable disables the ripple unless androidRipple is set. To preserve that behavior, set androidRipple on Android instead of the underlay/opacity/animation props above (don't combine them, or you'll get both a ripple and an underlay animation at once):
-
RectButtonandroidRipple={{}}
-
BorderlessButtonandroidRipple={{ borderless: true }} (matches the legacy borderless ripple shape)

See the Touchable docs for full Platform.select examples.

- Standard Buttons (Deprecated) - BaseButton, RectButton and BorderlessButton are still available but are now deprecated. They have been internally rewritten using the new Hooks API to resolve long-standing issues.

- Legacy Buttons (Deprecated): The original, pre-rewrite versions are still accessible, but have been renamed with a Legacy prefix (e.g., LegacyRectButton).

Although the legacy JS implementation of the buttons is still available, they also use the new host component internally. Because of that, PureNativeButton is no longer available in Gesture Handler 3.

<details>
<summary>Legacy buttons</summary>

| RNGH2 | RNGH3 |
| --------------------- | --------------------------- |
|
RawButton | LegacyRawButton |
|
BaseButton | LegacyBaseButton |
|
RectButton | LegacyRectButton |
|
BorderlessButton | LegacyBorderlessButton |
|
PureNativeButton | Not available in Gesture Handler 3 |

</details>

ReanimatedSwipeable

ReanimatedSwipeable has been rewritten using the new hook API. Additionally, enabled, hitSlop, enableTrackpadTwoFingerGesture, dragOffsetFromLeft and dragOffsetFromRight props now accept SharedValues.

dragOffsetFromRight now accepts negative values. If you were using it with positive values, make sure to change the sign when migrating.

Touchables

Touchable can also be used as a substitute for old, deprecated Touchables.
- To replace
TouchableOpacity, add activeOpacity={0.2} and animationDuration={{ in: 0, out: 150 }}.
- To replace
TouchableHighlight, carry underlayColor and activeOpacity over unchanged and add activeUnderlayOpacity={1} so the underlay is solid. A perfect 1:1 replacement isn't possible: in TouchableHighlight the container's own background becomes the underlay and activeOpacity dims just the children on top (the underlay shows through the dimmed children), while Touchable renders the underlay as a separate layer between background and children, and its activeOpacity dims the whole component. The visual feedback may differ slightly because of the different layering.
- To replace
TouchableNativeFeedback, add androidRipple={{ foreground: true }} — the legacy component defaults to useForeground: true, so { foreground: true } is the closest match. Drop foreground only when the original code set useForeground={false}; add color, radius, or borderless if it customized the background prop.
-
TouchableWithoutFeedback can be replaced with a plain Touchable.

Other components

Other components have also been internally rewritten using the new hook API but are exported under their original names, so no changes are necessary on your part. However, if you need to use the previous implementation for any reason, the old components are also available and are prefixed with Legacy, e.g., ScrollView is now available as LegacyScrollView.

<details>
<summary>Legacy components</summary>

| RNGH2 | RNGH3 |
| --------------------- | --------------------------- |
|
ScrollView | LegacyScrollView |
|
FlatList | LegacyFlatList |
|
RefreshControl | LegacyRefreshControl |
|
Switch | LegacySwitch |
|
TextInput | LegacyTextInput |
|
DrawerLayoutAndroid | LegacyDrawerLayoutAndroid |

</details>

createNativeWrapper

createNativeWrapper is deprecated and it is now exported as legacy_createNativeWrapper.

Replaced types

Most of the types, like TapGesture, are still present in Gesture Handler 3. However, they are now used in new hook API. Types for old API now have Legacy prefix, e.g. TapGesture becomes LegacyTapGesture.

<details>
<summary>Legacy types</summary>

| RNGH2 | RNGH3 |
| ----------------------- | ----------------------------- |
|
PanGesture | LegacyPanGesture |
|
TapGesture | LegacyTapGesture |
|
LongPressGesture | LegacyLongPressGesture |
|
RotationGesture | LegacyRotationGesture |
|
PinchGesture | LegacyPinchGesture |
|
FlingGesture | LegacyFlingGesture |
|
HoverGesture | LegacyHoverGesture |
|
NativeGesture | LegacyNativeGesture |
|
ManualGesture | LegacyManualGesture |
|
ForceTouchGesture | LegacyForceTouchGesture |
| | |
|
ComposedGesture | LegacyComposedGesture |
|
RaceGesture | LegacyRaceGesture |
|
SimultaneousGesture | LegacySimultaneousGesture |
|
ExclusiveGesture | LegacyExclusiveGesture |
| | |
|
RawButtonProps | LegacyRawButtonProps |
|
BaseButtonProps | LegacyBaseButtonProps |
|
RectButtonProps | LegacyRectButtonProps |
|
BorderlessButtonProps | LegacyBorderlessButtonProps |

</details>

---

Packages/Docs Gesture Handler/Docs/Legacy Gestures/ Category .Json (packages/docs-gesture-handler/docs/legacy-gestures/_category_.json)

{
"label": "Legacy Gesture Handler 2",
"position": 8,
"link": {
"type": "generated-index"
}
}

---

Packages/Docs Gesture Handler/Docs/Legacy Gestures/ Shared/V2 Info (packages/docs-gesture-handler/docs/legacy-gestures/_shared/v2-info.md)

:::warning
Heads up! This page covers the old builder-based API from Gesture Handler 2. We recommend checking out the newer hook-based API from Gesture Handler 3.
:::

================================================
SYMLINK: packages/docs-gesture-handler/docs/legacy-gestures/_shared/base-continuous-gesture-callbacks.md -> base-continuous-gesture-callbacks.md
================================================

================================================
SYMLINK: packages/docs-gesture-handler/docs/legacy-gestures/_shared/base-continuous-gesture-config.md -> base-continuous-gesture-config.md
================================================

================================================
SYMLINK: packages/docs-gesture-handler/docs/legacy-gestures/_shared/base-gesture-callbacks.md -> base-gesture-callbacks.md
================================================

================================================
SYMLINK: packages/docs-gesture-handler/docs/legacy-gestures/_shared/base-gesture-config.md -> base-gesture-config.md
================================================

================================================
SYMLINK: packages/docs-gesture-handler/docs/legacy-gestures/_shared/base-gesture-event-data.md -> base-gesture-event-data.md
================================================

================================================
SYMLINK: packages/docs-gesture-handler/docs/legacy-gestures/_shared/gesture-detector-functional1.md -> gesture-detector-functional1.md
================================================

================================================
SYMLINK: packages/docs-gesture-handler/docs/legacy-gestures/fling-gesture.md -> fling-gesture.md
================================================

================================================
SYMLINK: packages/docs-gesture-handler/docs/legacy-gestures/force-touch-gesture.md -> force-touch-gesture.md
================================================

================================================
SYMLINK: packages/docs-gesture-handler/docs/legacy-gestures/gesture-composition.md -> gesture-composition.md
================================================

================================================
SYMLINK: packages/docs-gesture-handler/docs/legacy-gestures/gesture-detector.md -> gesture-detector.md
================================================

================================================
SYMLINK: packages/docs-gesture-handler/docs/legacy-gestures/gesture.md -> gesture.md
================================================

================================================
SYMLINK: packages/docs-gesture-handler/docs/legacy-gestures/hover-gesture.md -> hover-gesture.md
================================================

================================================
SYMLINK: packages/docs-gesture-handler/docs/legacy-gestures/long-press-gesture.md -> long-press-gesture.md
================================================

================================================
SYMLINK: packages/docs-gesture-handler/docs/legacy-gestures/manual-gesture.md -> manual-gesture.md
================================================

================================================
SYMLINK: packages/docs-gesture-handler/docs/legacy-gestures/native-gesture.md -> native-gesture.md
================================================

================================================
SYMLINK: packages/docs-gesture-handler/docs/legacy-gestures/pan-gesture.md -> pan-gesture.md
================================================

================================================
SYMLINK: packages/docs-gesture-handler/docs/legacy-gestures/pinch-gesture.md -> pinch-gesture.md
================================================

================================================
SYMLINK: packages/docs-gesture-handler/docs/legacy-gestures/rotation-gesture.md -> rotation-gesture.md
================================================

================================================
SYMLINK: packages/docs-gesture-handler/docs/legacy-gestures/state-manager.md -> state-manager.md
================================================

================================================
SYMLINK: packages/docs-gesture-handler/docs/legacy-gestures/tap-gesture.md -> tap-gesture.md
================================================

================================================
SYMLINK: packages/docs-gesture-handler/docs/legacy-gestures/touch-events.md -> touch-events.md
================================================

---

Packages/Docs Gesture Handler/Docs/Under The Hood/ Category .Json (packages/docs-gesture-handler/docs/under-the-hood/_category_.json)

{
"label": "Under the hood",
"position": 7,
"link": {
"type": "generated-index"
}
}

---

Packages/Docs Gesture Handler/Docs/Under The Hood/How Does It Work (packages/docs-gesture-handler/docs/under-the-hood/how-does-it-work.md)

---
id: how-does-it-work
title: How does it work?
sidebar_label: How does it work?
---

Units

All handler component properties and event attributes that represent onscreen dimensions are expressed in screen density independent units we refer to as "points".
These are the units commonly used in the React Native ecosystem (e.g. in the layout system).
They do not map directly to physical pixels but instead to iOS's points and to dp units on Android.

iOS

All gestures are implemented using UIGestureRecognizers. Some of them have been slightly modified to allow for more customization and to conform to the state flow of RNGH. When you assign a gesture configuration to the GestureDetector, it creates all the required recognizers and assigns them to the child view of the detector. From this point, most of the heavy lifting is handled by the UIKit (with our help to correctly implement interactions between gestures).

Android

Unfortunately, Android doesn't provide an easy way of handling gestures, hence most of them were implemented from scratch, including a system for managing how the gestures should interact with each other. Here's a quick overview of how it works:
When you wrap a component with
GestureHandlerRootView, it allows for the RNGH to intercept all touch events on that component and process them, deciding whether they should be handled by one of the gesture handlers or passed to the underlying view. Gesture handlers are created right at the moment you declare them in order to initialize all of the necessary handlers natively. Every GestureHandlerRootView also has a specific handler to decide whether to pass the touch events or to consume them. It can never activate, only begin, end or be cancelled. When this handler is in the UNDETERMINED state, it means that there is no touch in progress; however, when the touch starts it transitions to the BEGAN state. As long as it stays in that state, no touch event is consumed, but as soon as it gets cancelled (meaning that some handler has activated) all incoming touch events get consumed, preventing the underlying view from receiving them.

When a pointer touches the screen, the view tree is traversed in order to extract all handlers attached to the views below the finger (including the one attached to the GestureHandlerRootView) and all extracted handlers transition to the BEGAN state, signalling that the gesture may have begun. The touch events continue to be delivered to all extracted handlers until one of them recognizes the gesture and tries to activate. At this point, the orchestrator checks whether this gesture should wait for any other of the extracted gestures to fail. If it does, it's put to the waiting list, if it doesn't, it gets activated and all other gestures (that are not simultaneous with it) get cancelled. When a gesture handler transitions to a finished state (the gesture recognized by it stops, fails or gets cancelled) the orchestrator checks the waiting handlers. Every one of them that waited for the gesture that just failed tries to activate again (and again the orchestrator checks if it should wait for any of the extracted gestures...).

---

Packages/Docs Gesture Handler/Docs/Under The Hood/State (packages/docs-gesture-handler/docs/under-the-hood/state.mdx)

---
id: state
title: Handler State
sidebar_label: Handler State
---

import GestureStateFlowExample from '@site/src/examples/GestureStateFlowExample';

Gesture handlers can be treated as "state machines".
At any given time, each handler instance has an assigned state that can change when new touch events occur or can be forced to change by the touch system in certain circumstances.

States manage the internal recognition process. You can hook into these transitions using specific gesture callbacks.

| State | Description | Callback |
| :----------------- | :--------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
UNDETERMINED | The default initial state of every handler. | — |
|
BEGAN | The handler has started receiving touch data but hasn't yet met the activation criteria. | onBegin |
|
ACTIVE | The gesture is recognized and activation criteria are met. | onActivate when it first transitions into the ACTIVE state. <br /><br /> onUpdate when it has new data about the gesture. |
|
END | The user successfully completed the gesture. | onDeactivate with event.canceled set to false. <br/><br/> onFinalize with event.canceled set to false. |
|
FAILED | The handler failed to recognize the gesture. | onDeactivate if the gesture was in ACTIVE state before, with event.canceled set to true. <br/><br/> onFinalize with event.canceled set to true. |
|
CANCELLED | The system interrupted the gesture. | onDeactivate if the gesture was in ACTIVE state before, with event.canceled set to true. <br/><br/> onFinalize with event.canceled set to true. |

State flows

The most typical flow of state is when a gesture picks up on an initial touch event, then recognizes it, then acknowledges its ending and resets itself back to the initial state. Drag or hold the circle below to see how each state machine reacts.

<GestureStateFlowExample />

---

Packages/Docs Gesture Handler/Versioned Docs/Version 1.X/About Handlers (packages/docs-gesture-handler/versioned_docs/version-1.x/about-handlers.md)

---
id: about-handlers
title: About Gesture Handlers
sidebar_label: About Gesture Handlers
---

Gesture handlers are the core building blocks of this library.
We use this term to describe elements of the native touch system that the library allows us to instantiate and control from JavaScript using React's Component interface.

Each handler type is capable of recognizing one type of gesture (pan, pinch, etc.) and provides gesture-specific information via events (translation, scale, etc.).

Handlers analyze touch stream synchronously in the UI thread. This allows for uninterrupted interactions even when the JavaScript thread is blocked.

Each handler works as an isolated state machine. It takes touch stream as an input and based on it, it can flip between states.
When a gesture starts, based on the position where the finger was placed, a set of handlers that may be interested in recognizing the gesture is selected.
All the touch events (touch down, move, up, or when other fingers are placed or lifted) are delivered to all of the handlers selected initially.
When one gesture becomes active, it cancels all the other gestures (read more about how to influence this process in "Cross handler interactions" section).

Gesture handler components do not instantiate a native view in the view hierarchy. Instead, they are kept in the library's own registry and are only connected to native views. When using any of the gesture handler components, it is important for it to have a native view rendered as a child.
Since handler components don't have corresponding views in the hierarchy, the events registered with them are actually hooked into the underlying view.

Available gesture handlers

Currently, the library provides the following list of gestures. Their parameters and attributes they provide to gesture events are documented under each gesture page:

- PanGestureHandler
-
TapGestureHandler
-
LongPressGestureHandler
-
RotationGestureHandler
-
FlingGestureHandler
-
PinchGestureHandler
-
ForceTouchGestureHandler

Discrete vs continuous

We distinguish between two types of gestures: discrete and continuous.

Continuous gesture handlers can be active for a long period of time and will generate a stream of gesture events until the gesture is over.
An example of a continuous handler is
PanGestureHandler that once activated, will start providing updates about translation and other properties.

On the other hand, discrete gesture handlers once activated will not stay in the active state but will end immediately.
LongPressGestureHandler is a discrete handler, as it only detects if the finger is placed for a sufficiently long period of time, it does not track finger movements (as that's the responsibility of PanGestureHandler).

Keep in mind that onGestureEvent is only generated in continuous gesture handlers and shouldn't be used in the TapGestureHandler and other discrete handlers.

Nesting handlers

Handler components can be nested. In any case it is recommended that the innermost handler renders a native view component. There are some limitations that apply when using useNativeDriver flag. An example of nested handlers:

js
class Multitap extends Component {
render() {
return (
<LongPressGestureHandler
onHandlerStateChange={this._onLongpress}
minDurationMs={800}>
<TapGestureHandler
onHandlerStateChange={this._onSingleTap}
waitFor={this.doubleTapRef}>
<TapGestureHandler
ref={this.doubleTapRef}
onHandlerStateChange={this._onDoubleTap}
numberOfTaps={2}>
<View style={styles.box} />
</TapGestureHandler>
</TapGestureHandler>
</LongPressGestureHandler>
);
}
}
text

Using native components

Gesture handler library exposes a set of components normally available in React Native that are wrapped in NativeViewGestureHandler.
Here is a list of exposed components:

- ScrollView
-
FlatList
-
Switch
-
TextInput
-
DrawerLayoutAndroid (Android only)

If you want to use other handlers or buttons nested in a ScrollView, use the waitFor property to define the interaction between a handler and ScrollView.

Events with useNativeDriver

Because handlers do not instantiate native views but instead hook up to their child views, directly nesting two gesture handlers using Animated.event is not currently supported.
To workaround this limitation we recommend placing an
<Animated.View> component in between the handlers.

Instead of doing:

js
const PanAndRotate = () => (
<PanGestureHandler onGestureEvent={Animated.event({ ... }, { useNativeDriver: true })}>
<RotationGestureHandler onGestureEvent={Animated.event({ ... }, { useNativeDriver: true })}>
<Animated.View style={animatedStyles}/>
</RotationGestureHandler>
</PanGestureHandler>
);
text
Place an <Animated.View> in between the handlers:
js
const PanAndRotate = () => (
<PanGestureHandler onGestureEvent={Animated.event({ ... }, { useNativeDriver: true })}>
<Animated.View>
<RotationGestureHandler onGestureEvent={Animated.event({ ... }, { useNativeDriver: true })}>
<Animated.View style={animatedStyles}/>
</RotationGestureHandler>
</Animated.View>
</PanGestureHandler>
);
text
Another consequence of handlers depending on their native child components is that when using a useNativeDriver flag with an Animated.event, the child component must be wrapped by an Animated.API e.g. <Animated.View> instead of just a <View>:
js
class Draggable extends Component {
render() {
return (
<PanGestureHandler onGestureEvent={Animated.event({ ... }, { useNativeDriver: true })}>
<Animated.View style={animatedStyles} /> {/ <-- NEEDS TO BE Animated.View /}
</PanGestureHandler>
);
}
};
text
---

Packages/Docs Gesture Handler/Versioned Docs/Version 1.X/Contributing (packages/docs-gesture-handler/versioned_docs/version-1.x/contributing.md)

---
id: contributing
title: Contributing
---

If you are interested in the project and want to contribute or support it in other ways don't hesitate to contact me on Twitter!

All PRs are welcome, but talk to us before you start working on something big.

The easiest way to get started with contributing code is by:
- Reviewing the list of open issues and trying to solve the ones that seem approachable to you.
- Updating the documentation whenever you see some information is unclear, missing or out of date.

Code is only one way you can contribute. You may want to consider replying on issues if you know how to help.

---

Packages/Docs Gesture Handler/Versioned Docs/Version 1.X/Credits (packages/docs-gesture-handler/versioned_docs/version-1.x/credits.md)

---
id: credits
title: Credits
---

This project is supported by amazing people from Expo.io and Software Mansion.

[](https://expo.io)
[](https://swmansion.com)

---

Packages/Docs Gesture Handler/Versioned Docs/Version 1.X/Example (packages/docs-gesture-handler/versioned_docs/version-1.x/example.md)

---
id: example
title: Running Example App
---

import useBaseUrl from '@docusaurus/useBaseUrl';
import GifGallery from '@site/components/GifGallery'

Example app code is located under Example/ folder in the repo.
It showcases the majority of the Gesture Handler library features.
The app consists of the list of single screen examples presenting the capabilities of the library.
Each example is located under a separate folder under
Example/.

<GifGallery>
<img src={useBaseUrl("gifs/sampleapp.gif")} width="180" height="320" />
</GifGallery>

Running example app on Expo

You can run example app on Expo. Follow instructions under this link to do so. Note that the app published to Expo is not the most up to date version. We publish updates whenever new version of Expo SDK is released. If you wish to try the most up to date version you will have to run example app locally. For that see below 👇

Running example app locally

Before you begin you should follow React Native's setup steps to make sure you have all the tools necessary to build and run React Native apps installed.
The example app is a regular React Native app, so in case of problems or to learn about available commands you may want to check react-native cli documentation.

In order to run example app you need to clone the repo first:

bash
git clone [email protected]:software-mansion/react-native-gesture-handler.git
text
Then go to the library folder:
bash
cd react-native-gesture-handler/
text
Install dependencies of library with the following command:
bash
yarn
text
Then go to the Example folder:
bash
cd Example
text
Install dependencies of example with the following command:
bash
yarn
text
Run development server:
bash
yarn start
text
Finally run one of the commands below in order to build, install and launch the app on Android:
bash
react-native run-android
text
or on iOS:
bash
react-native run-ios
text
You will need to have an Android or iOS device or emulator connected and react-native-cli package installed globally.

---

Packages/Docs Gesture Handler/Versioned Docs/Version 1.X/Getting Started (packages/docs-gesture-handler/versioned_docs/version-1.x/getting-started.md)

---
id: getting-started
title: Getting Started
sidebar_label: Getting Started
slug: /
---

Gesture Handler aims to replace React Native's built in touch system called Gesture Responder System.

The motivation for building this library was to address the performance limitations of React Native's Gesture Responder System and to provide more control over the built-in native components that can handle gestures.
We recommend this talk by Krzysztof Magiera in which he explains issues with the responder system.

In a nutshell, the library provides:

- A way to use a platform's native touch handling system for recognizing pinch, rotation and pan (besides a few other gestures).
- The ability to define relations between gesture handlers, e.g. when you have a pan handler in
ScrollView you can make that ScrollView wait until it knows pan won't recognize.
- Mechanisms to use touchables that run in native thread and follow platform default behavior; e.g. in the event they are in a scrollable component, turning into pressed state is slightly delayed to prevent it from highlighting when you fling.
- The possibility to implement smooth gesture interactions thanks to Animated Native Driver &mdash; interactions will be responsive even when the JS thread is overloaded.

Installation

Requirements

| version | react-native version |
| --------- | ---------------------- |
| 1.4.0+ | 0.60.0+ |
| 1.1.0+ | 0.57.2+ |
| &lt;1.1.0 | 0.50.0+ |

It may be possible to use newer versions of react-native-gesture-handler on React Native with version &lt;= 0.59 by reverse Jetifying.
Read more on that here https://github.com/mikehardy/jetifier#to-reverse-jetify--convert-node_modules-dependencies-to-support-libraries

Note that if you wish to use React.createRef() support for interactions you need to use v16.3 of React.

Expo

#### Managed Expo

To use the version of react-native-gesture-handler that is compatible with your managed Expo project, run expo install react-native-gesture-handler.

The Expo SDK incorporates the latest version of react-native-gesture-handler available at the time of each SDK release, so managed Expo apps might not always support all our latest features as soon as they are available.

#### Bare React Native

Since the library uses native support for handling gestures, it requires an extended installation to the norm. If you are starting a new project, you may want to initialize it with expo-cli and use a bare template, they come pre-installed with react-native-gesture-handler.

JS

First, install the library using yarn:

bash
yarn add react-native-gesture-handler
text
or with npm if you prefer:
bash
npm install --save react-native-gesture-handler
text
After installation, wrap your entry point with <GestureHandlerRootView>.

For example:

js
export default function App() {
return <GestureHandlerRootView>{/ content /}</GestureHandlerRootView>;
}
text
:::info
If you use props such as
shouldCancelWhenOutside, simultaneousHandlers, waitFor etc. with gesture handlers, the handlers need to be mounted under a single GestureHandlerRootView. So it's important to keep the GestureHandlerRootView as close to the actual root view as possible.

Note that GestureHandlerRootView acts like a normal View. So if you want it to fill the screen, you will need to pass { flex: 1 } like you'll need to do with a normal View. By default, it'll take the size of the content nested inside.
:::

:::tip
If you're using gesture handler in your component library, you may want to wrap your library's code in the GestureHandlerRootView component. This will avoid extra configuration for the user.
:::

#### Linking

Important: You only need to do this step if you're using React Native 0.59 or lower. Since v0.60, linking happens automatically.
bash
react-native link react-native-gesture-handler
text

Android

Follow the steps below:

If you use one of the _native navigation libraries_ (e.g.
wix/react-native-navigation),
you should follow this separate guide to get
gesture handler library set up on Android. Ignore the rest of this step – it
only applies to RN apps that use a standard Android project layout.

#### Updating MainActivity.java

Update your MainActivity.java file (or wherever you create an instance of ReactActivityDelegate), so that it overrides the method responsible for creating ReactRootView instance and then use the root view wrapper provided by this library. Do not forget to import ReactActivityDelegate, ReactRootView, and RNGestureHandlerEnabledRootView:

diff
package com.swmansion.gesturehandler.react.example;

import com.facebook.react.ReactActivity;
+ import com.facebook.react.ReactActivityDelegate;
+ import com.facebook.react.ReactRootView;
+ import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;

public class MainActivity extends ReactActivity {

@Override
protected String getMainComponentName() {
return "Example";
}

+ @Override
+ protected ReactActivityDelegate createReactActivityDelegate() {
+ return new ReactActivityDelegate(this, getMainComponentName()) {
+ @Override
+ protected ReactRootView createRootView() {
+ return new RNGestureHandlerEnabledRootView(MainActivity.this);
+ }
+ };
+ }
}

text
#### Usage with modals on Android

On Android RNGH does not work by default because modals are not located under React Native Root view in native hierarchy.
To fix that, components need to be wrapped with
GestureHandlerRootView.

For example:

js
export default function Example() {
return (
<Modal>
<GestureHandlerRootView>
<DraggableBox />
</GestureHandlerRootView>
</Modal>
);
}
text

iOS

There is no additional configuration required on iOS except what follows in the next steps.

If you're in a CocoaPods project (the default setup since React Native 0.60),
make sure to install pods before you run your app:

bash
cd ios && pod install
text
For React Native 0.61 or greater, add the library as the first import in your index.js file:
js
import 'react-native-gesture-handler';
text

With wix/react-native-navigation

If you are using a native navigation library like wix/react-native-navigation you need to follow a different setup for your Android app to work properly. The reason is that both native navigation libraries and Gesture Handler library need to use their own special subclasses of ReactRootView.

Instead of changing Java code you will need to wrap every screen component using GestureHandlerRootView on the JS side. This can be done for example at the stage when you register your screens. Here's an example:

js
import { Navigation } from 'react-native-navigation';

import FirstTabScreen from './FirstTabScreen';
import SecondTabScreen from './SecondTabScreen';
import PushedScreen from './PushedScreen';

// register all screens of the app (including internal ones)
export function registerScreens() {
Navigation.registerComponent(
'example.FirstTabScreen',
() => {
return (
<GestureHandlerRootView>
<FirstTabScreen />
</GestureHandlerRootView>
);
},
() => FirstTabScreen
);
Navigation.registerComponent(
'example.SecondTabScreen',
() => {
return (
<GestureHandlerRootView>
<SecondTabScreen />
</GestureHandlerRootView>
);
},
() => SecondTabScreen
);
Navigation.registerComponent(
'example.PushedScreen',
() => {
return (
<GestureHandlerRootView>
<PushedScreen />
</GestureHandlerRootView>
);
},
() => PushedScreen
);
}

text
You can check out this example project to see this kind of set up in action.

Remember that you need to wrap each screen that you use in your app with GestureHandlerRootView as with native navigation libraries each screen maps to a separate root view. It will not be enough to wrap the main screen only.

Testing

In order to load mocks provided by the library add following to your jest config in package.json:

json
"setupFiles": ["./node_modules/react-native-gesture-handler/jestSetup.js"]
text
Example:
json
"jest": {
"preset": "react-native",
"setupFiles": ["./node_modules/react-native-gesture-handler/jestSetup.js"]
}
text
---

Packages/Docs Gesture Handler/Versioned Docs/Version 1.X/Interactions (packages/docs-gesture-handler/versioned_docs/version-1.x/interactions.md)

---
id: interactions
title: Cross handler interactions
sidebar_label: Cross handler interactions
---

Gesture handlers can "communicate" with each other to support complex gestures and control how they _activate_ in certain scenarios.

There are two means of achieving that described in the sections below.
In each case, it is necessary to provide a reference of one handler as a property to the other.
Gesture handler relies on ref objects created using
React.createRef(), introduced in React 16.3.

Simultaneous recognition

By default, only one gesture handler is allowed to be in the ACTIVE state.
So when a gesture handler recognizes a gesture it cancels all other handlers in the
BEGAN state and prevents any new handlers from receiving a stream of touch events as long as it remains ACTIVE.

This behavior can be altered using the simultaneousHandlers property (available for all types of handlers).
This property accepts a ref or an array of refs to other handlers.
Handlers connected in this way will be allowed to remain in the
ACTIVE state at the same time.

Use cases

Simultaneous recognition needs to be used when implementing a photo preview component that supports zooming (scaling) the photo, rotating and panning it while zoomed in.
In this case we would use a
PinchGestureHandler, RotationGestureHandler and PanGestureHandler that would have to simultaneously recognize gestures.

Example

See the "Scale, rotate & tilt" example from the GestureHandler Example App or view it directly on your phone by visiting our expo demo.

js
class PinchableBox extends React.Component {
// ...take a look on full implementation in an Example app
render() {
const imagePinch = React.createRef();
const imageRotation = React.createRef();
return (
<RotationGestureHandler
ref={imageRotation}
simultaneousHandlers={imagePinch}
onGestureEvent={this._onRotateGestureEvent}
onHandlerStateChange={this._onRotateHandlerStateChange}>
<Animated.View>
<PinchGestureHandler
ref={imagePinch}
simultaneousHandlers={imageRotation}
onGestureEvent={this._onPinchGestureEvent}
onHandlerStateChange={this._onPinchHandlerStateChange}>
<Animated.View style={styles.container} collapsable={false}>
<Animated.Image
style={[
styles.pinchableImage,
{
/ events-related transformations /
},
]}
/>
</Animated.View>
</PinchGestureHandler>
</Animated.View>
</RotationGestureHandler>
);
}
}
text

Awaiting other handlers

Use cases

A good example where awaiting is necessary is when we want to have single and double tap handlers registered for one view (a button).
In such a case we need to make single tap handler await a double tap.
Otherwise if we try to perform a double tap the single tap handler will fire just after we hit the button for the first time, consequently cancelling the double tap handler.

Example

See the "Multitap" example from GestureHandler Example App or view it directly on your phone by visiting our expo demo.

js
const doubleTap = React.createRef();
const PressBox = () => (
<TapGestureHandler
onHandlerStateChange={({ nativeEvent }) =>
nativeEvent.state === State.ACTIVE && Alert.alert('Single tap!')
}
waitFor={doubleTap}>
<TapGestureHandler
ref={doubleTap}
onHandlerStateChange={({ nativeEvent }) =>
nativeEvent.state === State.ACTIVE && Alert.alert("You're so fast")
}
numberOfTaps={2}>
<View style={styles.box} />
</TapGestureHandler>
</TapGestureHandler>
);
text
---

Packages/Docs Gesture Handler/Versioned Docs/Version 1.X/Resources (packages/docs-gesture-handler/versioned_docs/version-1.x/resources.md)

---
id: resources
title: Learning Resources
---

Apps

Gesture Handler Example App – official gesture handler "showcase" app.

Gesture Handler Example on Expo – the official app you can install and play with using Expo.

Talks and workshops

Declarative future of gestures and animations in React Native by Krzysztof Magiera - talk that explains motivation behind creating gesture handler library. It also presents react-native-reanimated and how and when it can be used with gesture handler.

React Native workshop with Expo team @ReactEurope 2018 by Brent Vatne – great workshop explaining gesture handler in details and presenting a few exercises that will help get you started.

Living in an async world of React Native by Krzysztof Magiera – talk which highlights some issue with the React Native's touch system Gesture Handler aims to address. Also the motivation for building this library is explained.

React Native Touch & Gesture by Krzysztof Magiera - talk explaining JS responder system limitations and points out some of the core features of Gesture Handler.

---

Packages/Docs Gesture Handler/Versioned Docs/Version 1.X/State (packages/docs-gesture-handler/versioned_docs/version-1.x/state.md)

---
id: state
title: Handler State
sidebar_label: Handler State
---

As described in "About Gesture Handlers", gesture handlers can be treated as "state machines".
At any given time, each handler instance has an assigned state that can change when new touch events occur or can be forced to change by the touch system in certain circumstances.

A gesture handler can be in one of the six possible states:

- UNDETERMINED
- FAILED
- BEGAN
- CANCELLED
- ACTIVE
- END

Each state has its own description below.

Accessing state

We can monitor a handler's state changes by using the onHandlerStateChange callback and the destructured nativeEvent argument passed to it.
This can be done by comparing the
nativeEvent's state attribute to one of the constants exported under the State object (see example below).


import { State, LongPressGestureHandler } from 'react-native-gesture-handler';

class Demo extends Component {
_handleStateChange = ({ nativeEvent }) => {
if (nativeEvent.state === State.ACTIVE) {
Alert.alert('Longpress');
}
};
render() {
return (
<LongPressGestureHandler onHandlerStateChange={this._handleStateChange}>
<Text style={styles.buttonText}>Longpress me</Text>
</LongPressGestureHandler>
);
}
}

text

State flows

The most typical flow of state is when a gesture handler picks up on an initial touch event then recognizes it then acknowledges its ending then resets itself back to the initial state.

The flow looks as follows (longer arrows represent that there are possibly more touch events received before the state changes):

UNDETERMINED -> BEGAN ------> ACTIVE ------> END -> UNDETERMINED

Another possible flow is when a handler receives touches that cause a recognition failure:

UNDETERMINED -> BEGAN ------> FAILED -> UNDETERMINED

At last, when a handler does properly recognize the gesture but then is interrupted by the touch system. In that case, the gesture recognition is canceled and the flow looks as follows:

UNDETERMINED -> BEGAN ------> ACTIVE ------> CANCELLED -> UNDETERMINED

States

The section below describes all possible handler states:

UNDETERMINED

This is the initial state of each handler and it goes into this state after it's done recognizing a gesture.

FAILED

A handler received some touches but for some reason didn't recognize them. For example, if a finger travels more distance than a defined maxDist property allows, then the handler won't become active but will fail instead. Afterwards, its state will be reset to UNDETERMINED.

BEGAN

Handler has started receiving touch stream but hasn't yet received enough data to either fail or activate.

CANCELLED

The gesture recognizer has received a signal (possibly new touches or a command from the touch system controller) resulting in the cancellation of a continuous gesture. The gesture's state will become CANCELLED until it is finally reset to the initial state, UNDETERMINED.

ACTIVE

Handler has recognized a gesture. It will become and stay in the ACTIVE state until the gesture finishes (e.g. when user lifts the finger) or gets cancelled by the touch system. Under normal circumstances the state will then turn into END. In the case that a gesture is cancelled by the touch system, its state would then become CANCELLED.
Learn about discrete and continuous handlers here to understand how long a handler can be kept in the
ACTIVE state.

END

The gesture recognizer has received touches signalling the end of a gesture. Its state will become END until it is reset to UNDETERMINED.

---

Packages/Docs Gesture Handler/Versioned Docs/Version 1.X/Troubleshooting (packages/docs-gesture-handler/versioned_docs/version-1.x/troubleshooting.md)

---
id: troubleshooting
title: Troubleshooting
---

Troubleshooting

Thanks for giving this library a try! We are sorry that you might have encountered issues though. Here is how you can seek help:

1. Search over the issues on Github. There is a chance someone had this problem in the past and it has been resolved!

2. When sure your problem hasn't been reported or was reported but the proposed solution doesn't work for you please follow our issue reporting guidelines.

3. You can try seeking help on Expo Developers Slack channel where we often hang out.

4. If you feel like reading the source code I highly recommend it, as this is by far the best resource and gives you the most up to date insights into how the library works and what might be causing the bug.

5. If you managed to find the solution consider contributing a fix or update our documentation to make this information easier to find for the others in the future.

Reporting issues

This library is maintained by a very small team.
Please be mindful of that when reporting an issue and when it happens that we can't get back to you as soon as you might expect.
We would love to fix all the problems as soon as possible, but often our time is constrained by other issues/features or projects.
To make it easier for us to understand your issue and to be able to approach it sooner you can help by:
- Making sure the issue description is complete. Please include all the details about your environment (library version, RN version, device OS etc).
- It is the best to provide an example app that reproduces the issue you are having. Put it up on gist, snack or create a repo on Github – it doesn't matter as long as we can easily pull it in, run and see the issue.
- Explain how you run your repro app and what steps to take to reproduce the issue.
- Isolate your issue from other dependencies you might be using and make the repro app as minimal as possible.
- If you have spent some time figuring out the root cause of the problem you can leave a note about your findings so far.
- __Do not comment on closed issues__. It is very unlikely that we are going to notice your comment in such a case. If the issue has been closed, but the proposed solution doesn't work for you, please open a new one providing all the information necessary and linking to the solution you have tried.

---

Packages/Docs Gesture Handler/Versioned Docs/Version 1.X/Api/Components/Buttons (packages/docs-gesture-handler/versioned_docs/version-1.x/api/components/buttons.mdx)

---
id: buttons
title: Buttons
sidebar_label: Buttons
---

import useBaseUrl from '@docusaurus/useBaseUrl';
import GifGallery from '@site/components/GifGallery';

<GifGallery>
<img src={useBaseUrl('gifs/samplebutton.gif')} width="280" />
</GifGallery>

Gesture handler library provides native components that can act as buttons. These can be treated as a replacement to TouchableHighlight or TouchableOpacity from RN core. Gesture handler's buttons recognize touches in native which makes the recognition process deterministic, allows for rendering ripples on Android in highly performant way (TouchableNativeFeedback requires that touch event does a roundtrip to JS before we can update ripple effect, which makes ripples lag a bit on older phones), and provides native and platform default interaction for buttons that are placed in a scrollable container (in which case the interaction is slightly delayed to prevent button from highlighting when you fling).

Currently Gesture handler library exposes three components that render native touchable elements under the hood:

- BaseButton
-
RectButton
-
BorderlessButton

On top of that all the buttons are wrapped with NativeViewGestureHandler and therefore allow for all the common gesture handler properties and NativeViewGestureHandler's extra properties to be applied to them.

IMPORTANT: In order to make buttons accessible, you have to wrap your children in a View with accessible and accessibilityRole="button" props.
Example:

javascript
// Not accessible:
const NotAccessibleButton = () => (
<RectButton onPress={this._onPress}>
<Text>Foo</Text>
</RectButton>
);
// Accessible:
const AccessibleButton = () => (
<RectButton onPress={this._onPress}>
<View accessible accessibilityRole="button">
<Text>Bar</Text>
</View>
</RectButton>
);
text
It is applicable for both iOS and Android platform. On iOS, you won't be able to even select the button, on Android you won't be able to click it in accessibility mode.

BaseButton

Can be used as a base class if you'd like to implement some custom interaction for when the button is pressed.

Below is a list of properties specific to BaseButton component:

onActiveStateChange

function that gets triggered when button changes from inactive to active and vice versa. It passes active state as a boolean variable as a first parameter for that method.

onPress

function that gets triggered when the button gets pressed (analogous to onPress in TouchableHighlight from RN core).

rippleColor (Android only)

defines color of native ripple animation used since API level 21.

exclusive

defines if more than one button could be pressed simultaneously. By default set to true.

RectButton

This type of button component should be used when you deal with rectangular elements or blocks of content that can be pressed, for example table rows or buttons with text and icons. This component provides a platform specific interaction, rendering a rectangular ripple on Android or highlighting the background on iOS and on older versions of Android. In addition to the props of BaseButton, it accepts the following:

Below is a list of properties specific to RectButton component:

underlayColor

this is the background color that will be dimmed when button is in active state.

activeOpacity (iOS only)

opacity applied to the underlay when button is in active state.

BorderlessButton

This type of button component should be used with simple icon-only or text-only buttons. The interaction will be different depending on platform: on Android a borderless ripple will be rendered (it means that the ripple will animate into a circle that can span outside of the view bounds), whereas on iOS the button will be dimmed (similar to how TouchableOpacity works). In addition to the props of BaseButton, it accepts the following:

Below is a list of properties specific to BorderlessButton component:

borderless (Android only)

set this to false if you want the ripple animation to render only within view bounds.

activeOpacity (iOS only)

opacity applied to the button when it is in an active state.

Design patterns

Components listed here were not designed to behave and look in the same way on both platforms but rather to be used for handling similar behaviour on iOS and Android taking into consideration their design concepts.

If you wish to get specific information about platforms design patterns, visit official Apple docs and Material.io guideline, which widely describe how to implement coherent design.

This library allows to use native components with native feedback in adequate situations.

If you do not wish to implement custom design approach, RectButton and BorderlessButton seem to be absolutely enough and there's no need to use anything else. In all the remaining cases you can always rely on BaseButton which is a superclass for the other button classes and can be used as a generic Touchable replacement that can be customized to your needs.

Below we list some of the common usecases for button components to be used along with the type of button that should be used according to the platform specific design guidelines.

Lists and action buttons

If you have a list with clickable items or have an action button that need to display as a separate UI block (vs being inlined in a text) you should use RectButton. It changes opacity on click and additionally supports a ripple effect on Android.

<GifGallery>
<img src={useBaseUrl('gifs/androidsettings.gif')} width="280" />
<img src={useBaseUrl('gifs/iossettings.gif')} width="280" />
</GifGallery>

To determine emphasis of button it's vital to use fill color or leave it transparent especially on Android.
For medium emphasis you may consider outlined buttons which are used for lower impact than fill buttons.

<GifGallery>
<img src={useBaseUrl('gifs/androidbutton.gif')} width="280" />
</GifGallery>

Icon or text only buttons

Use BorderlessButton for simple icon-only or text-only buttons. The interaction will be different depending on platform: on Android a borderless ripple will be rendered, whereas on iOS the button will be dimmed.
It should be used if you wish to handle non-crucial actions and supportive behaviour.

<GifGallery>
<img src={useBaseUrl('gifs/androidmail.gif')} width="280" />
<img src={useBaseUrl('gifs/iosmail.gif')} width="280" />
</GifGallery>

PureNativeButton

Use a PureNativeButton for accessing the native Component used to build the more complex buttons listed above.
It is normally not recommended to use, but it might be useful if you want to wrap it using Animated or Reanimated.

javascript
import {
createNativeWrapper,
PureNativeButton,
} from 'react-native-gesture-handler';
import Animated from 'react-native-reanimated';
const { event, Value, createAnimatedComponent } = Animated;

const AnimatedRawButton = createNativeWrapper(
createAnimatedComponent(PureNativeButton),
{
shouldCancelWhenOutside: false,
shouldActivateOnStart: false,
}
);

export default class App extends React.Component {
constructor(props) {
super(props);
const state = new Value();
this._onGestureEvent = event([
{
nativeEvent: { state },
},
]);
}

render() {
return <AnimatedRawButton onHandlerStateChange={this._onGestureEvent} />;
}
}

text
---

Packages/Docs Gesture Handler/Versioned Docs/Version 1.X/Api/Components/Drawer Layout (packages/docs-gesture-handler/versioned_docs/version-1.x/api/components/drawer-layout.mdx)

---
id: drawer-layout
title: Drawer Layout
sidebar_label: DrawerLayout
---

import useBaseUrl from '@docusaurus/useBaseUrl';
import GifGallery from '@site/components/GifGallery';

This is a cross-platform replacement for React Native's DrawerLayoutAndroid component. It provides a compatible API but allows for the component to be used on both Android and iOS. Please refer to React Native docs for the detailed usage for standard parameters.

Usage:

DrawerLayout component isn't exported by default from the react-native-gesture-handler package. To use it, import it in the following way:

js
import DrawerLayout from 'react-native-gesture-handler/DrawerLayout';
text

Properties:

On top of the standard list of parameters DrawerLayout has an additional set of attributes to customize its behavior. Please refer to the list below:

drawerType

possible values are: front, back or slide (default is front). It specifies the way the drawer will be displayed. When set to front the drawer will slide in and out along with the gesture and will display on top of the content view. When back is used the drawer displays behind the content view and can be revealed with gesture of pulling the content view to the side. Finally slide option makes the drawer appear like it is attached to the side of the content view; when you pull both content view and drawer will follow the gesture.

Type slide:

<GifGallery>
<img src={useBaseUrl('gifs/drawer-slide.gif')} width="280" />
</GifGallery>

Type front:

<GifGallery>
<img src={useBaseUrl('gifs/drawer-front.gif')} width="280" />
</GifGallery>

Type back:

<GifGallery>
<img src={useBaseUrl('gifs/drawer-back.gif')} width="280" />
</GifGallery>

edgeWidth

number, allows for defining how far from the edge of the content view the gesture should activate.

hideStatusBar

boolean, when set to true Drawer component will use StatusBar API to hide the OS status bar whenever the drawer is pulled or when its in an "open" state.

statusBarAnimation

possible values are: slide, none or fade (defaults to slide). Can be used when hideStatusBar is set to true and will select the animation used for hiding/showing the status bar. See StatusBar documentation for more details.

overlayColor

color (default to "black") of a semi-transparent overlay to be displayed on top of the content view when drawer gets open. A solid color should be used as the opacity is added by the Drawer itself and the opacity of the overlay is animated (from 0% to 70%).

renderNavigationView

function. This attribute is present in the standard implementation already and is one of the required params. The gesture handler version of DrawerLayout makes it possible for the function passed as renderNavigationView to take an Animated value as a parameter that indicates the progress of the drawer opening/closing animation (progress value is 0 when closed and 1 when opened). This can be used by the drawer component to animated its children while the drawer is opening or closing.

onDrawerClose

function. This function is called when the drawer is closed.

onDrawerOpen

function. This function is called when the drawer is opened.

onDrawerSlide

function. This function is called as a drawer sliding open from touch events. The progress of the drawer opening/closing is passed back as 0 when closed and 1 when opened.

onDrawerStateChanged

function. This function is called when the status of the drawer changes. Possible values that can be passed back are: 'Idle', 'Dragging', and 'Settling'.

enableTrackpadTwoFingerGesture (iOS only)

Enables two-finger gestures on supported devices, for example iPads with trackpads. If not enabled the gesture will require click + drag, with enableTrackpadTwoFingerGesture swiping with two fingers will also trigger the gesture.

children

component or function. Children is a component which is rendered by default and is wrapped by drawer. However, it could also be a render function which takes an Animated value as a parameter that indicates the progress of the drawer opening/closing animation (progress value is 0 when closed and 1 when opened) in the same way as the renderNavigationView prop.

Methods

openDrawer(options)

openDrawer can take an optional options parameter which is an object, enabling further customization of the open animation.

options has two optional properties:

velocity: number, the initial velocity of the object attached to the spring. Default 0 (object is at rest).
speed: number, controls speed of the animation. Default 12.

closeDrawer(options)

closeDrawer can take an optional options parameter which is an object, enabling further customization of the close animation.

options has two optional properties:

velocity: number, the initial velocity of the object attached to the spring. Default 0 (object is at rest).
speed: number, controls speed of the animation. Default 12.

Example:

See the drawer example from GestureHandler Example App or view it directly on your phone by visiting our expo demo.

js
class Drawerable extends Component {
handleDrawerSlide = (status) => {
// outputs a value between 0 and 1
console.log(status);
};

renderDrawer = () => {
return (
<View>
<Text>I am in the drawer!</Text>
</View>
);
};

render() {
return (
<View style={{ flex: 1 }}>
<DrawerLayout
drawerWidth={200}
drawerPosition={DrawerLayout.positions.Right}
drawerType="front"
drawerBackgroundColor="#ddd"
renderNavigationView={this.renderDrawer}
onDrawerSlide={this.handleDrawerSlide}>
<View>
<Text>Hello, it's me</Text>
</View>
</DrawerLayout>
</View>
);
}
}

text
---

Packages/Docs Gesture Handler/Versioned Docs/Version 1.X/Api/Components/Swipeable (packages/docs-gesture-handler/versioned_docs/version-1.x/api/components/swipeable.md)

---
id: swipeable
title: Swipeable
sidebar_label: Swipeable
---

import useBaseUrl from '@docusaurus/useBaseUrl';
import GifGallery from '@site/components/GifGallery'

<GifGallery>
<img src={useBaseUrl("gifs/sampleswipeable.gif")} height="120" />
</GifGallery>

This component allows for implementing swipeable rows or similar interaction. It renders its children within a pannable container that allows for horizontal swiping left and right. While swiping, one of two "action" containers can be shown depending on whether the user swipes left or right (containers can be rendered by renderLeftActions or renderRightActions props).

Usage:

Similarly to the DrawerLayout, Swipeable component isn't exported by default from the react-native-gesture-handler package. To use it, import it in the following way:

js
import Swipeable from 'react-native-gesture-handler/Swipeable';
text

Properties

friction

a number that specifies how much the visual interaction will be delayed compared to the gesture distance. e.g. value of 1 will indicate that the swipeable panel should exactly follow the gesture, 2 means it is going to be two times "slower".

leftThreshold

distance from the left edge at which released panel will animate to the open state (or the open panel will animate into the closed state). By default it's a half of the panel's width.

rightThreshold

distance from the right edge at which released panel will animate to the open state (or the open panel will animate into the closed state). By default it's a half of the panel's width.

overshootLeft

a boolean value indicating if the swipeable panel can be pulled further than the left actions panel's width. It is set to true by default as long as the left panel render method is present.

overshootRight

a boolean value indicating if the swipeable panel can be pulled further than the right actions panel's width. It is set to true by default as long as the right panel render method is present.

overshootFriction

a number that specifies how much the visual interaction will be delayed compared to the gesture distance at overshoot. Default value is 1, which means no friction; for a native feel, try 8 or above.

onSwipeableLeftOpen

method that is called when left action panel gets open.

onSwipeableRightOpen

method that is called when right action panel gets open.

onSwipeableOpen

method that is called when action panel gets open (either right or left).

onSwipeableClose

method that is called when action panel is closed.

onSwipeableLeftWillOpen

method that is called when left action panel starts animating on open.

onSwipeableRightWillOpen

method that is called when right action panel starts animating on open.

onSwipeableWillOpen

method that is called when action panel starts animating on open (either right or left).

onSwipeableWillClose

method that is called when action panel starts animating on close.

renderLeftActions

method that is expected to return an action panel that is going to be revealed from the left side when user swipes right.
This map describes the values to use as inputRange for extra interpolation:
AnimatedValue: [startValue, endValue]

progressAnimatedValue: [0, 1]
dragAnimatedValue: [0, +]

To support rtl flexbox layouts use flexDirection styling.

renderRightActions

method that is expected to return an action panel that is going to be revealed from the right side when user swipes left.
This map describes the values to use as inputRange for extra interpolation:
AnimatedValue: [startValue, endValue]

progressAnimatedValue: [0, 1]
dragAnimatedValue: [0, -]

To support rtl flexbox layouts use flexDirection styling.

containerStyle

style object for the container (Animated.View), for example to override overflow: 'hidden'.

childrenContainerStyle

style object for the children container (Animated.View), for example to apply flex: 1.

enableTrackpadTwoFingerGesture (iOS only)

Enables two-finger gestures on supported devices, for example iPads with trackpads. If not enabled the gesture will require click + drag, with enableTrackpadTwoFingerGesture swiping with two fingers will also trigger the gesture.

Methods

Using reference to Swipeable it's possible to trigger some actions on it

close

method that closes component.

openLeft

method that opens component on left side.

openRight

method that opens component on right side.

Example:

See the swipeable example from GestureHandler Example App or view it directly on your phone by visiting our expo demo.

js
import React, { Component } from 'react';
import { Animated, StyleSheet, View } from 'react-native';
import { RectButton } from 'react-native-gesture-handler';
import Swipeable from 'react-native-gesture-handler/Swipeable';

class AppleStyleSwipeableRow extends Component {
renderLeftActions = (progress, dragX) => {
const trans = dragX.interpolate({
inputRange: [0, 50, 100, 101],
outputRange: [-20, 0, 0, 1],
});
return (
<RectButton style={styles.leftAction} onPress={this.close}>
<Animated.Text
style={[
styles.actionText,
{
transform: [{ translateX: trans }],
},
]}>
Archive
</Animated.Text>
</RectButton>
);
};
render() {
return (
<Swipeable renderLeftActions={this.renderLeftActions}>
<Text>"hello"</Text>
</Swipeable>
);
}
}

text
---

Packages/Docs Gesture Handler/Versioned Docs/Version 1.X/Api/Components/Touchables (packages/docs-gesture-handler/versioned_docs/version-1.x/api/components/touchables.md)

---
id: touchables
title: Touchables
sidebar_label: Touchables
---

Gesture Handler library provides an implementation of RN's touchable components that are based on native buttons and do not rely on the JS responder system utilized by RN. Our touchable implementation follows the same API and aims to be a drop-in replacement for touchables available in React Native.

React Native's touchables API can be found here:

- Touchable Native Feedback
- Touchable Highlight
- Touchable Opacity
- Touchable Without Feedback

All major touchable properties (except from pressRetentionOffset) have been adopted and should behave in a similar way as with RN's touchables.

The motivation for using RNGH touchables as a replacement for these imported from React Native is to follow built-in native behavior more closely by utilizing platform native touch system instead of relying on the JS responder system.
These touchables and their feedback behavior are deeply integrated with native
gesture ecosystem and could be connected with other native components (e.g.
ScrollView) and Gesture Handlers easily and in a more predictable way, which
follows native apps' behavior.

Our intention was to make the switch for these touchables as simple as possible. In order to use RNGH's touchables, the only thing you need to do is change the library from which you import touchable components.

Example:

javascript
import {
TouchableNativeFeedback,
TouchableHighlight,
TouchableOpacity,
TouchableWithoutFeedback,
} from 'react-native';
text
has to be replaced with:
javascript
import {
TouchableNativeFeedback,
TouchableHighlight,
TouchableOpacity,
TouchableWithoutFeedback,
} from 'react-native-gesture-handler';
text
For a comparison of both touchable implementations see our touchables example

---

Packages/Docs Gesture Handler/Versioned Docs/Version 1.X/Api/Gesture Handlers/Common Gh (packages/docs-gesture-handler/versioned_docs/version-1.x/api/gesture-handlers/common-gh.md)

---
id: common-gh
title: Common handler properties
sidebar_label: Common handler properties
---

This page covers the common set of properties all gesture handler components expose.

Units

All handler component properties and event attributes that represent onscreen dimensions are expressed in screen density independent units we refer to as "points".
These are the units commonly used in React Native ecosystem (e.g. in the layout system).
They do not map directly to physical pixels but instead to iOS's points and to dp units on Android.

Properties

This section describes properties that can be used with all gesture handler components:

enabled

Accepts a boolean value.
Indicates whether the given handler should be analyzing stream of touch events or not.
When set to
false we can be sure that the handler's state will never become ACTIVE.
If the value gets updated while the handler has already started recognizing a gesture, then the handler's state will immediately change to
FAILED or CANCELLED (depending on its current state).
Default value is
true.

shouldCancelWhenOutside

Accepts a boolean value.
When
true the handler will cancel or fail recognition (depending on its current state) whenever the finger leaves the area of the connected view.
Default value of this property is different depending on the handler type.
Most handlers'
shouldCancelWhenOutside property defaults to false except for the LongPressGestureHandler and TapGestureHandler which default to true.

simultaneousHandlers

Accepts a react ref object or an array of refs to other handler components (refs should be created using React.createRef()). When set, the handler will be allowed to activate even if one or more of the handlers provided by their refs are in an ACTIVE state. It will also prevent the provided handlers from cancelling the current handler when they activate. Read more in the cross handler interaction section.

waitFor

Accepts a react ref object or an array of refs to other handler components (refs should be created using React.createRef()). When set the handler will not activate as long as the handlers provided by their refs are in the BEGAN state. Read more in the cross handler interaction section.

hitSlop

This parameter enables control over what part of the connected view area can be used to begin recognizing the gesture.
When a negative number is provided the bounds of the view will reduce the area by the given number of points in each of the sides evenly.

Instead you can pass an object to specify how each boundary side should be reduced by providing different number of points for left, right, top or bottom sides.
You can alternatively provide
horizontal or vertical instead of specifying directly left, right or top and bottom.
Finally, the object can also take
width and height attributes.
When
width is set it is only allowed to specify one of the sides right or left.
Similarly when
height is provided only top or bottom can be set.
Specifying
width or height is useful if we only want the gesture to activate on the edge of the view. In which case for example we can set left: 0 and width: 20 which would make it possible for the gesture to be recognized when started no more than 20 points from the left edge.

IMPORTANT: Note that this parameter is primarily designed to reduce the area where gesture can activate. Hence it is only supported for all the values (except width and height) to be non positive (0 or lower). Although on Android it is supported for the values to also be positive and therefore allow to expand beyond view bounds but not further than the parent view bounds. To achieve this effect on both platforms you can use React Native's View hitSlop property.

onGestureEvent

Takes a callback that is going to be triggered for each subsequent touch event while the handler is in an ACTIVE state. Event payload depends on the particular handler type. Common set of event data attributes is documented below and handler specific attributes are documented on the corresponding handler pages. E.g. event payload for PinchGestureHandler contains scale attribute that represents how the distance between fingers changed since when the gesture started.

Instead of a callback Animated.event object can be used. Also Animated events with useNativeDriver flag enabled are fully supported.

onHandlerStateChange

Takes a callback that is going to be triggered when state of the given handler changes.

The event payload contains the same payload as in case of onGestureEvent including handler specific event attributes some handlers may provide.

In addition onHandlerStateChange event payload contains oldState attribute which represents the state of the handler right before the change.

Instead of a callback Animated.event object can be used. Also Animated events with useNativeDriver flag enabled are fully supported.

Event data

This section describes the attributes of event object being provided to onGestureEvent and onHandlerStateChange callbacks:

state

Current state of the handler. Expressed as one of the constants exported under State object by the library. Refer to the section about handler state to learn more about how to use it.

numberOfPointers

Represents the number of pointers (fingers) currently placed on the screen.

---

Packages/Docs Gesture Handler/Versioned Docs/Version 1.X/Api/Gesture Handlers/Create Native Wrapper (packages/docs-gesture-handler/versioned_docs/version-1.x/api/gesture-handlers/create-native-wrapper.md)

---
id: create-native-wrapper
title: createNativeWrapper
sidebar_label: createNativeWrapper()
---

Creates provided component with NativeViewGestureHandler, allowing it to be part of RNGH's
gesture system.

Arguments

Component

The component we want to wrap.

config

Config is an object with properties that can be used on NativeViewGestureHandler

Returns

Wrapped component.

---

Packages/Docs Gesture Handler/Versioned Docs/Version 1.X/Api/Gesture Handlers/Fling Gh (packages/docs-gesture-handler/versioned_docs/version-1.x/api/gesture-handlers/fling-gh.md)

---
id: fling-gh
title: FlingGestureHandler
sidebar_label: Fling
---

A discrete gesture handler that activates when the movement is sufficiently long and fast.
Handler gets ACTIVE when movement is sufficiently long and it does not take too much time.
When handler gets activated it will turn into END state when finger is released.
The handler will fail to recognize if the finger is lifted before being activated.
The handler is implemented using UISwipeGestureRecognizer on iOS and from scratch on Android.

Properties

See set of properties inherited from base handler class. Below is a list of properties specific to FlingGestureHandler component:

direction

Expresses the allowed direction of movement. It's possible to pass one or many directions in one parameter:

js
direction={Directions.RIGHT | Directions.LEFT}
text
or
js
direction={Directions.DOWN}
text

numberOfPointers

Determines the exact number of pointers required to handle the fling gesture.

Event data

See set of event attributes from base handler class. Below is a list of gesture event attributes specific to FlingGestureHandler:

x

X coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the handler. Expressed in point units.

y

Y coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the handler. Expressed in point units.

absoluteX

X coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the root view. The value is expressed in point units. It is recommended to use it instead of x in cases when the original view can be transformed as an effect of the gesture.

absoluteY

Y coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the root view. The value is expressed in point units. It is recommended to use it instead of y in cases when the original view can be transformed as an effect of the gesture.

Example

See the fling example from GestureHandler Example App or view it directly on your phone by visiting our expo demo.

js
const LongPressButton = () => (
<FlingGestureHandler
direction={Directions.RIGHT | Directions.LEFT}
onHandlerStateChange={({ nativeEvent }) => {
if (nativeEvent.state === State.ACTIVE) {
Alert.alert("I'm flinged!");
}
}}>
<View style={styles.box} />
</FlingGestureHandler>
);
text
---

Packages/Docs Gesture Handler/Versioned Docs/Version 1.X/Api/Gesture Handlers/Force Gh (packages/docs-gesture-handler/versioned_docs/version-1.x/api/gesture-handlers/force-gh.md)

---
id: force-gh
title: ForceTouchGestureHandler (iOS only)
sidebar_label: Force touch
---

A continuous gesture handler that recognizes force of a touch. It allows for tracking pressure of touch on some iOS devices.
The handler activates when the pressure of a touch is greater than or equal to
minForce. It fails if the pressure is greater than maxForce.
Gesture callback can be used for continuous tracking of the touch pressure. It provides information for one finger (the first one).

At the beginning of the gesture, the pressure factor is 0.0. As the pressure increases, the pressure factor increases proportionally. The maximum pressure is 1.0.

The handler is implemented using custom UIGestureRecognizer on iOS. There's no implementation provided on Android and it simply renders children without any wrappers.
Since this behaviour is only provided on some iOS devices, this handler should not be used for defining any crucial behaviors. Use it only as an additional improvement and make all features accessible without this handler as well.

Properties

See set of properties inherited from base handler class. Below is a list of properties specific to ForceTouchGestureHandler component:

minForce

A minimal pressure that is required before handler can activate. Should be a value from range [0.0, 1.0]. Default is 0.2.

maxForce

A maximal pressure that could be applied for handler. If the pressure is greater, handler fails. Should be a value from range [0.0, 1.0].

feedbackOnActivation

Boolean value defining if haptic feedback has to be performed on activation.

Event data

See set of event attributes from base handler class. Below is a list of gesture event attributes specific to ForceTouchGestureHandler:

force

The pressure of a touch.

Static method

forceTouchAvailable

You may check if it's possible to use ForceTouchGestureHandler with ForceTouchGestureHandler.forceTouchAvailable

Example

See the force touch handler example from GestureHandler Example App or view it directly on your phone by visiting our expo demo.

js
<ForceTouchGestureHandler
minForce={0}
onGestureEvent={this._onGestureEvent}
onHandlerStateChange={this._onHandlerStateChange}>
<Animated.View
style={[
styles.box,
{ transform: [{ scale: Animated.add(1, this.force) }] },
]}
/>
</ForceTouchGestureHandler>
text
---

Packages/Docs Gesture Handler/Versioned Docs/Version 1.X/Api/Gesture Handlers/Longpress Gh (packages/docs-gesture-handler/versioned_docs/version-1.x/api/gesture-handlers/longpress-gh.md)

---
id: longpress-gh
title: LongPressGestureHandler
sidebar_label: Long press
---

A discrete gesture handler that activates when the corresponding view is pressed for a sufficiently long time.
This handler's state will turn into END immediately after the finger is released.
The handler will fail to recognize a touch event if the finger is lifted before the minimum required time or if the finger is moved further than the allowable distance.

The handler is implemented using UILongPressGestureRecognizer on iOS and LongPressGestureHandler on Android.

Properties

See set of properties inherited from base handler class. Below is a list of properties specific to the LongPressGestureHandler component:

minDurationMs

Minimum time, expressed in milliseconds, that a finger must remain pressed on the corresponding view. The default value is 500.

maxDist

Maximum distance, expressed in points, that defines how far the finger is allowed to travel during a long press gesture. If the finger travels further than the defined distance and the handler hasn't yet activated, it will fail to recognize the gesture. The default value is 10.

Event data

See set of event attributes from base handler class. Below is a list of gesture event attributes specific to the LongPressGestureHandler component:

x

X coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the handler.

y

Y coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the handler.

absoluteX

X coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the root view. It is recommended to use absoluteX instead of x in cases when the view attached to the handler can be transformed as an effect of the gesture.

absoluteY

Y coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the root view. It is recommended to use absoluteY instead of y in cases when the view attached to the handler can be transformed as an effect of the gesture.

duration

Duration of the long press (time since the start of the event), expressed in milliseconds.

Example

See the multitap example from GestureHandler Example App or view it directly on your phone by visiting our expo demo.

js
const LongPressButton = () => (
<LongPressGestureHandler
onHandlerStateChange={({ nativeEvent }) => {
if (nativeEvent.state === State.ACTIVE) {
Alert.alert("I'm being pressed for so long");
}
}}
minDurationMs={800}>
<View style={styles.box} />
</LongPressGestureHandler>
);
text
---

Packages/Docs Gesture Handler/Versioned Docs/Version 1.X/Api/Gesture Handlers/Nativeview Gh (packages/docs-gesture-handler/versioned_docs/version-1.x/api/gesture-handlers/nativeview-gh.md)

---
id: nativeview-gh
title: NativeViewGestureHandler
sidebar_label: NativeView
---

A gesture handler that allows other touch handling components to participate in
RNGH's gesture system.

Used by createNativeWrapper().

Properties

See set of properties inherited from base handler class. Below is a list of properties specific to NativeViewGestureHandler component:

shouldActivateOnStart (Android only)

When true, underlying handler will activate unconditionally when in BEGAN or UNDETERMINED state.

disallowInterruption

When true, cancels all other gesture handlers when this NativeViewGestureHandler receives an ACTIVE state event.

---

Packages/Docs Gesture Handler/Versioned Docs/Version 1.X/Api/Gesture Handlers/Pan Gh (packages/docs-gesture-handler/versioned_docs/version-1.x/api/gesture-handlers/pan-gh.md)

---
id: pan-gh
title: PanGestureHandler
sidebar_label: Pan
---

A continuous gesture handler that can recognize a panning (dragging) gesture and track its movement.

The handler activates when a finger is placed on the screen and moved some initial distance.

Configurations such as a minimum initial distance, specific vertical or horizontal pan detection and number of fingers required for activation (allowing for multifinger swipes) may be specified.

Gesture callback can be used for continuous tracking of the pan gesture. It provides information about the gesture such as its XY translation from the starting point as well as its instantaneous velocity.

The handler is implemented using UIPanGestureRecognizer on iOS and PanGestureHandler on Android.

Custom activation criteria

The PanGestureHandler component exposes a number of properties that can be used to customize the criteria under which a handler will activate or fail when recognizing a gesture.

When more than one of such a property is set, PanGestureHandler expects all criteria to be met for successful recognition and at most one of the criteria to be overstepped to fail recognition.
For example when both
minDeltaX and minDeltaY are set to 20 we expect the finger to travel by 20 points in both the X and Y axis before the handler activates.
Another example would be setting both
maxDeltaX and maxDeltaY to 20 and minDist to 23.
In such a case, if we move a finger along the X-axis by 20 points and along the Y-axis by 0 points, the handler will fail even though the finger is still within the bounds of translation along Y-axis.

Multi touch pan handling

If your app relies on multi touch pan handling, this section provides some information about how the default behavior differs between the platforms and how (if necessary) it can be unified.

The difference in multi touch pan handling lies in the way translation properties during the event are calculated.
On iOS the default behavior when more than one finger is placed on the screen is to treat this situation as if only one pointer was placed in the center of mass (average position of all the pointers).
This applies also to many platform native components that handle touch even if not primarily interested in multi touch interactions, like for example the UIScrollView component.

The default behavior for native components like scroll view, pager views or drawers is different and hence gesture handler defaults to that when it comes to pan handling.
The difference is that instead of treating the center of mass of all the fingers placed as a leading pointer it takes the latest placed finger as such.
This behavior can be changed on Android using
avgTouches flag.

Note that on both Android and iOS when the additional finger is placed on the screen that translation prop is not affected even though the position of the pointer being tracked might have changed.
Therefore it is safe to rely on translation most of the time as it only reflects the movement that happens regardless of how many fingers are placed on the screen and if that number changes over time.
If you wish to track the "center of mass" virtual pointer and account for its changes when the number of finger changes you can use relative or absolute position provided in the event (
x and y or absoluteX and absoluteY).

Properties

See set of properties inherited from base handler class. Below is a list of properties specific to PanGestureHandler component:

minDist

Minimum distance the finger (or multiple fingers) needs to travel before the handler activates. Expressed in points.

minPointers

The number of fingers that is required to be placed before the handler can activate. Should be an integer greater than or equal to 0.

maxPointers

When the given number of fingers is placed on the screen and the handler hasn't yet activated it will fail recognizing the gesture. Should be an integer greater than or equal to 0.

activeOffsetX

Range along the X axis (in points) where fingers travel without activation of the handler. Moving outside of this range implies activation of the handler. Range can be given as an array or a single number.
If the range is set as an array, the first value must be lower or equal to 0, and the second one higher or equal to 0.
If only one number
p is given a range of (-inf, p) will be used if p is higher or equal to 0 and (-p, inf) otherwise.

activeOffsetY

Range along the Y axis (in points) where fingers travel without activation of the handler. Moving outside of this range implies activation of the handler. Range can be given as an array or a single number.
If the range is set as an array, the first value must be lower or equal to 0, and the second one higher or equal to 0.
If only one number
p is given a range of (-inf, p) will be used if p is higher or equal to 0 and (-p, inf) otherwise.

failOffsetY

When the finger moves outside this range (in points) along the Y axis and the handler hasn't yet activated it will fail recognizing the gesture. Range can be given as an array or a single number.
If the range is set as an array, the first value must be lower or equal to 0, and the second one higher or equal to 0.
If only one number
p is given a range of (-inf, p) will be used if p is higher or equal to 0 and (-p, inf) otherwise.

failOffsetX

When the finger moves outside this range (in points) along the X axis and the handler hasn't yet activated it will fail recognizing the gesture. Range can be given as an array or a single number.
If the range is set as an array, the first value must be lower or equal to 0, and the second one higher or equal to 0.
If only one number
p is given a range of (-inf, p) will be used if p is higher or equal to 0 and (-p, inf) otherwise.

avgTouches (Android only)

enableTrackpadTwoFingerGesture (iOS only)

Enables two-finger gestures on supported devices, for example iPads with trackpads. If not enabled the gesture will require click + drag, with enableTrackpadTwoFingerGesture swiping with two fingers will also trigger the gesture.

Event data

See set of event attributes from base handler class. Below is a list of gesture event attributes specific to PanGestureHandler:

translationX

Translation of the pan gesture along X axis accumulated over the time of the gesture. The value is expressed in the point units.

translationY

Translation of the pan gesture along Y axis accumulated over the time of the gesture. The value is expressed in the point units.

velocityX

Velocity of the pan gesture along the X axis in the current moment. The value is expressed in point units per second.

velocityY

Velocity of the pan gesture along the Y axis in the current moment. The value is expressed in point units per second.

x

X coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the handler. Expressed in point units.

y

Y coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the handler. Expressed in point units.

absoluteX

X coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the root view. The value is expressed in point units. It is recommended to use it instead of x in cases when the original view can be transformed as an effect of the gesture.

absoluteY

Y coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the root view. The value is expressed in point units. It is recommended to use it instead of y in cases when the original view can be transformed as an effect of the gesture.

Example

See the draggable example from GestureHandler Example App or view it directly on your phone by visiting our expo demo.

js
const circleRadius = 30;
class Circle extends Component {
_touchX = new Animated.Value(windowWidth / 2 - circleRadius);
_onPanGestureEvent = Animated.event([{ nativeEvent: { x: this._touchX } }], {
useNativeDriver: true,
});
render() {
return (
<PanGestureHandler onGestureEvent={this._onPanGestureEvent}>
<Animated.View
style={{
height: 150,
justifyContent: 'center',
}}>
<Animated.View
style={[
{
backgroundColor: '#42a5f5',
borderRadius: circleRadius,
height: circleRadius * 2,
width: circleRadius * 2,
},
{
transform: [
{
translateX: Animated.add(
this._touchX,
new Animated.Value(-circleRadius)
),
},
],
},
]}
/>
</Animated.View>
</PanGestureHandler>
);
}
}
text
---

Packages/Docs Gesture Handler/Versioned Docs/Version 1.X/Api/Gesture Handlers/Pinch Gh (packages/docs-gesture-handler/versioned_docs/version-1.x/api/gesture-handlers/pinch-gh.md)

---
id: pinch-gh
title: PinchGestureHandler
sidebar_label: Pinch
---

A continuous gesture handler that recognizes a pinch gesture. It allows for tracking the distance between two fingers and using that information to scale or zoom your content.
The handler activates when fingers are placed on the screen and change their position.
Gesture callback can be used for continuous tracking of the pinch gesture. It provides information about velocity, anchor (focal) point of gesture and scale.

The distance between the fingers is reported as a scale factor. At the beginning of the gesture, the scale factor is 1.0. As the distance between the two fingers increases, the scale factor increases proportionally.
Similarly, the scale factor decreases as the distance between the fingers decreases.
Pinch gestures are used most commonly to change the size of objects or content onscreen.
For example, map views use pinch gestures to change the zoom level of the map.

The handler is implemented using UIPinchGestureRecognizer on iOS and from scratch on Android.

Properties

Properties provided to PinchGestureHandler do not extend the common set of properties from base handler class.

Event data

See set of event attributes from base handler class. Below is a list of gesture event attributes specific to PinchGestureHandler:

scale

The scale factor relative to the points of the two touches in screen coordinates.

velocity

Velocity of the pinch gesture at the current moment. The value is expressed in point units per second.

focalX

Position expressed in points along the X axis of the center anchor point of the gesture.

focalY

Position expressed in points along the Y axis of the center anchor point of the gesture.

Example

See the scale and rotation example from GestureHandler Example App or view it directly on your phone by visiting our expo demo.

js
export class PinchableBox extends React.Component {
_baseScale = new Animated.Value(1);
_pinchScale = new Animated.Value(1);
_scale = Animated.multiply(this._baseScale, this._pinchScale);
_lastScale = 1;
_onPinchGestureEvent = Animated.event(
[{ nativeEvent: { scale: this._pinchScale } }],
{ useNativeDriver: USE_NATIVE_DRIVER }
);

_onPinchHandlerStateChange = (event) => {
if (event.nativeEvent.oldState === State.ACTIVE) {
this._lastScale *= event.nativeEvent.scale;
this._baseScale.setValue(this._lastScale);
this._pinchScale.setValue(1);
}
};

render() {
return (
<PinchGestureHandler
onGestureEvent={this._onPinchGestureEvent}
onHandlerStateChange={this._onPinchHandlerStateChange}>
<View style={styles.container} collapsable={false}>
<Animated.Image
style={[
styles.pinchableImage,
{
transform: [{ perspective: 200 }, { scale: this._scale }],
},
]}
/>
</View>
</PinchGestureHandler>
);
}
}

text
---

Packages/Docs Gesture Handler/Versioned Docs/Version 1.X/Api/Gesture Handlers/Rotation Gh (packages/docs-gesture-handler/versioned_docs/version-1.x/api/gesture-handlers/rotation-gh.md)

---
id: rotation-gh
title: RotationGestureHandler
sidebar_label: Rotation
---

A continuous gesture handler that can recognize a rotation gesture and track its movement.

The handler activates when fingers are placed on the screen and change position in a proper way.

Gesture callback can be used for continuous tracking of the rotation gesture. It provides information about the gesture such as the amount rotated, the focal point of the rotation (anchor), and its instantaneous velocity.

The handler is implemented using UIRotationGestureRecognizer on iOS and from scratch on Android.

Properties

Properties provided to RotationGestureHandler do not extend common set of properties from base handler class.

Event data

See set of event attributes from base handler class. Below is a list of gesture event attributes specific to RotationGestureHandler:

rotation

Amount rotated, expressed in radians, from the gesture's focal point (anchor).

velocity

Instantaneous velocity, expressed in point units per second, of the gesture.

anchorX

X coordinate, expressed in points, of the gesture's central focal point (anchor).

anchorY

Y coordinate, expressed in points, of the gesture's central focal point (anchor).

Example

See the scale and rotation example from GestureHandler Example App or view it directly on your phone by visiting our expo demo.

js
class RotableBox extends React.Component {
_rotate = new Animated.Value(0);
_rotateStr = this._rotate.interpolate({
inputRange: [-100, 100],
outputRange: ['-100rad', '100rad'],
});
_lastRotate = 0;
_onRotateGestureEvent = Animated.event(
[{ nativeEvent: { rotation: this._rotate } }],
{ useNativeDriver: USE_NATIVE_DRIVER }
);
_onRotateHandlerStateChange = (event) => {
if (event.nativeEvent.oldState === State.ACTIVE) {
this._lastRotate += event.nativeEvent.rotation;
this._rotate.setOffset(this._lastRotate);
this._rotate.setValue(0);
}
};
render() {
return (
<RotationGestureHandler
onGestureEvent={this._onRotateGestureEvent}
onHandlerStateChange={this._onRotateHandlerStateChange}>
<Animated.Image
style={[
styles.pinchableImage,
{
transform: [{ perspective: 200 }, { rotate: this._rotateStr }],
},
]}
/>
</RotationGestureHandler>
);
}
}
text
---

Packages/Docs Gesture Handler/Versioned Docs/Version 1.X/Api/Gesture Handlers/Tap Gh (packages/docs-gesture-handler/versioned_docs/version-1.x/api/gesture-handlers/tap-gh.md)

---
id: tap-gh
title: TapGestureHandler
sidebar_label: Tap
---

A discrete gesture handler that recognizes one or many taps.

Tap gestures detect one or more fingers briefly touching the screen.
The fingers involved in these gestures must not move significantly from their initial touch positions.
The required number of taps and allowed distance from initial position may be configured.
For example, you might configure tap gesture recognizers to detect single taps, double taps, or triple taps.

In order for a handler to activate, specified gesture requirements such as minPointers, numberOfTaps, maxDist, maxDurationMs, and maxDelayMs (explained below) must be met. Immediately after the handler activates, it will END.

Properties

See set of properties inherited from base handler class. Below is a list of properties specific to the TapGestureHandler component:

minPointers

Minimum number of pointers (fingers) required to be placed before the handler activates. Should be a positive integer. The default value is 1.

maxDurationMs

Maximum time, expressed in milliseconds, that defines how fast a finger must be released after a touch. The default value is 500.

maxDelayMs

Maximum time, expressed in milliseconds, that can pass before the next tap — if many taps are required. The default value is 500.

numberOfTaps

Number of tap gestures required to activate the handler. The default value is 1.

maxDeltaX

Maximum distance, expressed in points, that defines how far the finger is allowed to travel along the X axis during a tap gesture. If the finger travels further than the defined distance along the X axis and the handler hasn't yet activated, it will fail to recognize the gesture.

maxDeltaY

Maximum distance, expressed in points, that defines how far the finger is allowed to travel along the Y axis during a tap gesture. If the finger travels further than the defined distance along the Y axis and the handler hasn't yet activated, it will fail to recognize the gesture.

maxDist

Maximum distance, expressed in points, that defines how far the finger is allowed to travel during a tap gesture. If the finger travels further than the defined distance and the handler hasn't yet activated, it will fail to recognize the gesture.

Event data

See set of event attributes from base handler class. Below is a list of gesture event attributes specific to the TapGestureHandler component:

x

X coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the handler.

y

Y coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the handler.

absoluteX

X coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the root view. It is recommended to use absoluteX instead of x in cases when the view attached to the handler can be transformed as an effect of the gesture.

absoluteY

Y coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the root view. It is recommended to use absoluteY instead of y in cases when the view attached to the handler can be transformed as an effect of the gesture.

Example

See the multitap example from GestureHandler Example App or view it directly on your phone by visiting our expo demo.

js
export class PressBox extends Component {
doubleTapRef = React.createRef();
render() {
return (
<TapGestureHandler
onHandlerStateChange={this._onSingleTap}
waitFor={this.doubleTapRef}>
<TapGestureHandler ref={this.doubleTapRef} numberOfTaps={2}>
<View style={styles.box} />
</TapGestureHandler>
</TapGestureHandler>
);
}
}
text
---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Components/ Category .Json (packages/docs-gesture-handler/versioned_docs/version-2.x/components/_category_.json)

{
"label": "Components",
"position": 4,
"link": {
"type": "generated-index"
}
}

---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Components/Buttons (packages/docs-gesture-handler/versioned_docs/version-2.x/components/buttons.mdx)

---
id: buttons
title: Buttons
sidebar_label: Buttons
---

import useBaseUrl from '@docusaurus/useBaseUrl';
import GifGallery from '@site/components/GifGallery';

<GifGallery>
<img src={useBaseUrl('gifs/samplebutton.gif')} width="280" />
</GifGallery>

Gesture handler library provides native components that can act as buttons. These can be treated as a replacement to TouchableHighlight or TouchableOpacity from RN core. Gesture handler's buttons recognize touches in native which makes the recognition process deterministic, allows for rendering ripples on Android in highly performant way (TouchableNativeFeedback requires that touch event does a roundtrip to JS before we can update ripple effect, which makes ripples lag a bit on older phones), and provides native and platform default interaction for buttons that are placed in a scrollable container (in which case the interaction is slightly delayed to prevent button from highlighting when you fling).

Currently Gesture handler library exposes three components that render native touchable elements under the hood:

- BaseButton
-
RectButton
-
BorderlessButton

On top of that all the buttons are wrapped with NativeViewGestureHandler and therefore allow for all the common gesture handler properties and NativeViewGestureHandler's extra properties to be applied to them.

IMPORTANT: In order to make buttons accessible, you have to wrap your children in a View with accessible and accessibilityRole="button" props.
Example:

javascript
// Not accessible:
const NotAccessibleButton = () => (
<RectButton onPress={this._onPress}>
<Text>Foo</Text>
</RectButton>
);
// Accessible:
const AccessibleButton = () => (
<RectButton onPress={this._onPress}>
<View accessible accessibilityRole="button">
<Text>Bar</Text>
</View>
</RectButton>
);
text
It is applicable for both iOS and Android platform. On iOS, you won't be able to even select the button, on Android you won't be able to click it in accessibility mode.

BaseButton

Can be used as a base class if you'd like to implement some custom interaction for when the button is pressed.

Below is a list of properties specific to BaseButton component:

onActiveStateChange

function that gets triggered when button changes from inactive to active and vice versa. It passes active state as a boolean variable as a first parameter for that method.

onPress

function that gets triggered when the button gets pressed (analogous to onPress in TouchableHighlight from RN core).

onLongPress

function that gets triggered when the button gets pressed for at least delayLongPress milliseconds.

rippleColor (Android only)

defines color of native ripple animation used since API level 21.

exclusive

defines if more than one button could be pressed simultaneously. By default set true.

delayLongPress

defines the delay, in milliseconds, after which the onLongPress callback gets called. By default set to 600.

RectButton

This type of button component should be used when you deal with rectangular elements or blocks of content that can be pressed, for example table rows or buttons with text and icons. This component provides a platform specific interaction, rendering a rectangular ripple on Android or highlighting the background on iOS and on older versions of Android. In addition to the props of BaseButton, it accepts the following:

Below is a list of properties specific to RectButton component:

underlayColor

this is the background color that will be dimmed when button is in active state.

activeOpacity (iOS only)

opacity applied to the underlay when button is in active state.

BorderlessButton

This type of button component should be used with simple icon-only or text-only buttons. The interaction will be different depending on platform: on Android a borderless ripple will be rendered (it means that the ripple will animate into a circle that can span outside of the view bounds), whereas on iOS the button will be dimmed (similar to how TouchableOpacity works). In addition to the props of BaseButton, it accepts the following:

Below is a list of properties specific to BorderlessButton component:

borderless (Android only)

set this to false if you want the ripple animation to render only within view bounds.

activeOpacity (iOS only)

opacity applied to the button when it is in an active state.

Design patterns

Components listed here were not designed to behave and look in the same way on both platforms but rather to be used for handling similar behaviour on iOS and Android taking into consideration their design concepts.

If you wish to get specific information about platforms design patterns, visit official Apple docs and Material.io guideline, which widely describe how to implement coherent design.

This library allows to use native components with native feedback in adequate situations.

If you do not wish to implement custom design approach, RectButton and BorderlessButton seem to be absolutely enough and there's no need to use anything else. In all the remaining cases you can always rely on BaseButton which is a superclass for the other button classes and can be used as a generic Touchable replacement that can be customized to your needs.

Below we list some of the common usecases for button components to be used along with the type of button that should be used according to the platform specific design guidelines.

Lists and action buttons

If you have a list with clickable items or have an action button that need to display as a separate UI block (vs being inlined in a text) you should use RectButton. It changes opacity on click and additionally supports a ripple effect on Android.

<GifGallery>
<img src={useBaseUrl('gifs/androidsettings.gif')} width="280" />
<img src={useBaseUrl('gifs/iossettings.gif')} width="280" />
</GifGallery>

To determine emphasis of button it's vital to use fill color or leave it transparent especially on Android.
For medium emphasis you may consider outlined buttons which are used for lower impact than fill buttons.

<GifGallery>
<img src={useBaseUrl('gifs/androidbutton.gif')} width="280" />
</GifGallery>

Icon or text only buttons

Use BorderlessButton for simple icon-only or text-only buttons. The interaction will be different depending on platform: on Android a borderless ripple will be rendered, whereas on iOS the button will be dimmed.
It should be used if you wish to handle non-crucial actions and supportive behaviour.

<GifGallery>
<img src={useBaseUrl('gifs/androidmail.gif')} width="280" />
<img src={useBaseUrl('gifs/iosmail.gif')} width="280" />
</GifGallery>

PureNativeButton

Use a PureNativeButton for accessing the native Component used for building more complex buttons listed above.
It is normally not recommended to use, but it might be useful if we want to wrap it using Animated or Reanimated.

javascript
import {
createNativeWrapper,
PureNativeButton,
} from 'react-native-gesture-handler';
import Animated from 'react-native-reanimated';
const { event, Value, createAnimatedComponent } = Animated;

const AnimatedRawButton = createNativeWrapper(
createAnimatedComponent(PureNativeButton),
{
shouldCancelWhenOutside: false,
shouldActivateOnStart: false,
}
);

export default class App extends React.Component {
constructor(props) {
super(props);
const state = new Value();
this._onGestureEvent = event([
{
nativeEvent: { state },
},
]);
}

render() {
return <AnimatedRawButton onHandlerStateChange={this._onGestureEvent} />;
}
}

text
---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Components/Pressable (packages/docs-gesture-handler/versioned_docs/version-2.x/components/pressable.mdx)

---
id: pressable
title: Pressable
sidebar_label: Pressable
---

import useBaseUrl from '@docusaurus/useBaseUrl';
import GifGallery from '@site/components/GifGallery';

<GifGallery>
<img src={useBaseUrl('gifs/pressable.gif')} width="70%" />
</GifGallery>

:::info
This component is a drop-in replacement for the
Pressable component.
:::

Pressable is a component that can detect various stages of tap, press, and hover interactions on any of its children.

Usage:

To use Pressable, import it in the following way:

js
import { Pressable } from 'react-native-gesture-handler';
text

Properties

children

either children or a render function that receives a boolean reflecting whether
the component is currently pressed.

style

either view styles or a function that receives a boolean reflecting whether
the component is currently pressed and returns view styles.

onPress

called after onPressOut when a single tap gesture is detected.

onPressIn

called before onPress when a touch is engaged.

onPressOut

called before onPress when a touch is released.

onLongPress

called immediately after pointer has been down for at least delayLongPress milliseconds (500 ms by default).

After onLongPress has been called, onPressOut will be called as soon as the pointer is lifted and onPress will not be called at all.

cancelable

whether a press gesture can be interrupted by a parent gesture such as a scroll event. Defaults to true.

onHoverIn (Web only)

called when pointer is hovering over the element.

onHoverOut (Web only)

called when pointer stops hovering over the element.

delayHoverIn (Web only)

duration to wait after hover in before calling onHoverIn.

delayHoverOut (Web only)

duration to wait after hover out before calling onHoverOut.

delayLongPress

duration (in milliseconds) from onPressIn before onLongPress is called.

disabled

whether the Pressable behavior is disabled.

hitSlop (Android & iOS only)

additional distance outside of the view in which a press is detected and onPressIn is triggered.

Accepts values of type number or Rect

pressRetentionOffset (Android & iOS only)

additional distance outside of the view (or hitSlop if present) in which a touch is considered a
press before
onPressOut is triggered.

Accepts values of type number or Rect

android_disableSound (Android only)

if true, doesn't play system sound on touch.

android_ripple (Android only)

enables the Android ripple effect and configures its color, radius and other parameters.

Accepts values of type RippleConfig

testOnly_pressed

used only for documentation or testing (e.g. snapshot testing).

unstable_pressDelay

duration (in milliseconds) to wait after press down before calling onPressIn.

Example:

See the full pressable example from GestureHandler example app.

import GestureStateFlowExample from '@site/src/examples/GestureStateFlowExample';

js
import { View, Text, StyleSheet } from 'react-native';
import { Pressable } from 'react-native-gesture-handler';

export default function Example() {
return (
<Pressable
style={({ pressed }) => (pressed ? styles.highlight : styles.pressable)}
hitSlop={20}
pressRetentionOffset={20}>
<View style={styles.textWrapper}>
<Text style={styles.text}>Pressable!</Text>
</View>
</Pressable>
);
}

const styles = StyleSheet.create({
pressable: {
width: 120,
height: 120,
backgroundColor: 'mediumpurple',
borderWidth: StyleSheet.hairlineWidth,
},
highlight: {
width: 120,
height: 120,
backgroundColor: 'red',
borderWidth: StyleSheet.hairlineWidth,
},
textWrapper: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
text: {
color: 'black',
},
});

text
---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Components/Reanimated Drawer Layout (packages/docs-gesture-handler/versioned_docs/version-2.x/components/reanimated-drawer-layout.mdx)

---
id: reanimated-drawer-layout
title: Reanimated Drawer Layout
sidebar_label: Reanimated Drawer Layout
---

import useBaseUrl from '@docusaurus/useBaseUrl';

Cross-platform replacement for the React Native's DrawerLayoutAndroid component.
For detailed usage of standard parameters, please refer to the React Native docs.

Usage:

To use it, import it in the following way:

js
import ReanimatedDrawerLayout from 'react-native-gesture-handler/ReanimatedDrawerLayout';
text

Properties:

drawerType

specifies the way the drawer will be displayed.
Accepts values of the
DrawerPosition enum. Defaults to FRONT.

- FRONT the drawer will be displayed above the content view.
-
BACK the drawer will be displayed below the content view, revealed by sliding away the content view.
-
SLIDE the drawer will appear attached to the content view, opening it slides both the drawer and the content view.

| FRONT | BACK | SLIDE |
| ----------------------------------------------------- | ---------------------------------------------------- | ----------------------------------------------------- |
| <img src={useBaseUrl('gifs/new-drawer-front.gif')} /> | <img src={useBaseUrl('gifs/new-drawer-back.gif')} /> | <img src={useBaseUrl('gifs/new-drawer-slide.gif')} /> |

edgeWidth

width of the invisible, draggable area on the edge of the content view, which can be dragged to open the drawer.

hideStatusBar

a boolean value. When set to true, drawer component will use StatusBar API to hide the OS status bar when the drawer is dragged or idle in the open position.

statusBarAnimation

a string with possible values: slide, none or fade. Defaults to slide.
May be used in combination with
hideStatusBar to select the animation used for hiding the status bar.
See StatusBar API docs.

overlayColor

color of the background overlay on top of the content window when the drawer is open.
This color's opacity animates from 0% to 100% as the drawer transitions from closed to open. Defaults to
rgba(0, 0, 0, 0.7).

renderNavigationView

a renderer function for the drawer component, provided with a progress parameter.

- progress - SharedValue that indicates the progress of drawer opening/closing animation.
- equals
0 when the drawer is closed and 1 when the drawer is opened
- can be used by the
drawer component to animated its children while the drawer is opening or closing

onDrawerClose

a function which is called when the drawer has been closed.

onDrawerOpen

a function which is called when the drawer has been opened.

onDrawerSlide

a function which is called when drawer is moving or animating, provided with a progress parameter.

- progress - SharedValue that indicates the progress of drawer opening/closing animation.
- equals
0 when the drawer is closed and 1 when the drawer is opened
- can be used by the
drawer component to animated its children while the drawer is opening or closing

onDrawerStateChanged

a function which is called when the status of the drawer changes. It takes two arguments:

- newState - interaction state of the drawer. It can be one of the following:
-
DrawerState.IDLE
-
DrawerState.DRAGGING
-
DrawerState.SETTLING
-
drawerWillShow - true when drawer started animating to open position, false otherwise.

enableTrackpadTwoFingerGesture (iOS only)

enables two-finger gestures on supported devices, for example iPads with trackpads.
If not enabled, the gesture will require click + drag, with
enableTrackpadTwoFingerGesture swiping with two fingers will also trigger the gesture.

children

either a component that's rendered in the content view or a function.
If
children is a function, it is provided with a progress parameter.

- progress - SharedValue that indicates the progress of drawer opening/closing animation.
- equals
0 when the drawer is closed and 1 when the drawer is opened
- can be used by the
drawer component to animated its children while the drawer is opening or closing

mouseButton(value: MouseButton) (Web & Android only)

allows users to choose which mouse button should handler respond to.
The enum
MouseButton consists of the following predefined fields:

- LEFT
-
RIGHT
-
MIDDLE
-
BUTTON_4
-
BUTTON_5
-
ALL

Arguments can be combined using | operator, e.g. mouseButton(MouseButton.LEFT | MouseButton.RIGHT). Defaults to MouseButton.LEFT.

enableContextMenu(value: boolean) (Web only)

specifies whether context menu should be enabled after clicking on underlying view with right mouse button. Defaults to false.

Methods

openDrawer(options)

openDrawer accepts an optional options parameter, which is an object with the following optional properties:

- initialVelocity - the initial velocity of the object attached to the spring. Defaults to 0.
-
animationSpeed - controls speed of the animation. Defaults to 1.

closeDrawer(options)

closeDrawer accepts an optional options parameter, which is an object with the following optional properties:

- initialVelocity - initial velocity of the object attached to the spring. Defaults to 0.
-
animationSpeed - controls speed of the animation. Defaults to 1.

Example:

See the reanimated drawer layout example from GestureHandler example app.

js
import React, { useRef } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';

import ReanimatedDrawerLayout, {
DrawerType,
DrawerPosition,
DrawerLayoutMethods,
} from 'react-native-gesture-handler/ReanimatedDrawerLayout';

const DrawerPage = () => {
return (
<View style={styles.drawerContainer}>
<Text>Lorem ipsum</Text>
</View>
);
};

export default function ReanimatedDrawerExample() {
const drawerRef = useRef < DrawerLayoutMethods > null;
const tapGesture = Gesture.Tap()
.runOnJS(true)
.onStart(() => drawerRef.current?.openDrawer());

return (
<ReanimatedDrawerLayout
ref={drawerRef}
renderNavigationView={() => <DrawerPage />}
drawerPosition={DrawerPosition.LEFT}
drawerType={DrawerType.FRONT}>
<View style={styles.innerContainer}>
<GestureDetector gesture={tapGesture}>
<View style={styles.box}>
<Text>Open drawer</Text>
</View>
</GestureDetector>
</View>
</ReanimatedDrawerLayout>
);
}

const styles = StyleSheet.create({
drawerContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'pink',
},
innerContainer: {
flex: 1,
backgroundColor: 'white',
alignItems: 'center',
justifyContent: 'center',
gap: 20,
},
box: {
padding: 20,
backgroundColor: 'pink',
},
});

text
---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Components/Reanimated Swipeable (packages/docs-gesture-handler/versioned_docs/version-2.x/components/reanimated_swipeable.md)

---
id: reanimated_swipeable
title: Reanimated Swipeable
sidebar_label: Reanimated Swipeable
---

import useBaseUrl from '@docusaurus/useBaseUrl';
import GifGallery from '@site/components/GifGallery'

<GifGallery>
<img src={useBaseUrl("gifs/sampleswipeable.gif")} height="120" />
</GifGallery>

:::info
This component is a drop-in replacement for the
Swipeable component, rewritten using Reanimated.
:::

Reanimated Swipeable allows for implementing swipeable rows or similar interaction. It renders its children within a pannable container and allows for horizontal swiping left and right. While swiping, one of two "action" containers can be shown depending on whether the user swipes left or right (containers can be rendered by renderLeftActions or renderRightActions props).

Usage:

To use it, import it in the following way:

js
import Swipeable from 'react-native-gesture-handler/ReanimatedSwipeable';
text

Properties

friction

a number that specifies how much the visual interaction will be delayed compared to the gesture distance.
e.g. value of
1 will indicate that the swipeable panel should exactly follow the gesture, 2 means it is going to be two times "slower".

leftThreshold

distance from the left edge at which released panel will animate to the open state (or the open panel will animate into the closed state). By default it's a half of the panel's width.

rightThreshold

distance from the right edge at which released panel will animate to the open state (or the open panel will animate into the closed state). By default it's a half of the panel's width.

dragOffsetFromLeftEdge

distance that the panel must be dragged from the left edge to be considered a swipe. The default value is 10.

dragOffsetFromRightEdge

distance that the panel must be dragged from the right edge to be considered a swipe. The default value is 10.

overshootLeft

a boolean value indicating if the swipeable panel can be pulled further than the left actions panel's width. It is set to true by default as long as the left panel render function is present.

overshootRight

a boolean value indicating if the swipeable panel can be pulled further than the right actions panel's width. It is set to true by default as long as the right panel render function is present.

overshootFriction

a number that specifies how much the visual interaction will be delayed compared to the gesture distance at overshoot. Default value is 1, it means no friction, for a native feel, try 8 or above.

onSwipeableOpen

a function that is called when swipeable is opened (either right or left).
Receives swipe direction as an argument.

onSwipeableClose

a function that is called when swipeable is closed.
Receives swipe direction as an argument.

onSwipeableWillOpen

a function that is called when swipeable starts animating on open (either right or left).
Receives swipe direction as an argument.

onSwipeableWillClose

a function that is called when swipeable starts animating on close.
Receives swipe direction as an argument.

onSwipeableOpenStartDrag

a function that is called when a user starts to drag the swipable to open.
Receives swipe direction as an argument.

onSwipeableCloseStartDrag

a function that is called when a user starts to drag the swipable to close.
Receives swipe direction as an argument.

renderLeftActions

a function that returns a component which will be rendered under the swipeable after swiping it to the right.
The function receives the following arguments:

- progress - a SharedValue representing swiping progress relative to the width of the returned element.
- Equals
0 when swipeable is closed, 1 when swipeable is opened.
- When the element overshoots it's opened position the value tends towards
Infinity.
-
translation - a horizontal offset of the swipeable relative to its closed position.
-
swipeableMethods - provides an object exposing the methods listed here.

This function must return a ReactNode.

To support rtl flexbox layouts use flexDirection styling.

renderRightActions

a function that returns a component which will be rendered under the swipeable after swiping it to the left.
The function receives the following arguments:

- progress - a SharedValue representing swiping progress relative to the width of the returned element.
- Equals
0 when swipeable is closed, 1 when swipeable is opened.
- When the element overshoots it's opened position the value tends towards
Infinity.
-
translation - a horizontal offset of the swipeable relative to its closed position.
-
swipeableMethods - provides an object exposing the methods listed here.

This function must return a ReactNode.

To support rtl flexbox layouts use flexDirection styling.

containerStyle

style object for the container (Animated.View), for example to override overflow: 'hidden'.

childrenContainerStyle

style object for the children container (Animated.View), for example to apply flex: 1.

simultaneousWithExternalGesture

A gesture configuration to be recognized simultaneously with the swipeable gesture. This is useful for allowing other gestures to work simultaneously with swipeable gesture handler.

For example, to enable a pan gesture alongside the swipeable gesture:

jsx
const panGesture = Gesture.Pan();

<GestureDetector gesture={panGesture}>
<ReanimatedSwipeable simultaneousWithExternalGesture={panGesture} />
</GestureDetector>

text
More details can be found in the gesture composition documentation.

enableTrackpadTwoFingerGesture (iOS only)

Enables two-finger gestures on supported devices, for example iPads with trackpads.
If not enabled the gesture will require click + drag, with
enableTrackpadTwoFingerGesture swiping with two fingers will also trigger the gesture.

mouseButton(value: MouseButton) (Web & Android only)

Allows users to choose which mouse button should handler respond to. The enum MouseButton consists of the following predefined fields:

- LEFT
-
RIGHT
-
MIDDLE
-
BUTTON_4
-
BUTTON_5
-
ALL

Arguments can be combined using | operator, e.g. mouseButton(MouseButton.LEFT | MouseButton.RIGHT). Default value is set to MouseButton.LEFT.

enableContextMenu(value: boolean) (Web only)

Specifies whether context menu should be enabled after clicking on underlying view with right mouse button. Default value is set to false.

Methods

Using reference to Swipeable it's possible to trigger some actions on it

close

a method that closes component.

openLeft

a method that opens component on left side.

openRight

a method that opens component on right side.

reset

a method that resets the swiping states of this Swipeable component.

Unlike method close, this method does not trigger any animation.

Example:

For a more in-depth presentation of differences between the new and the legacy implementations,
see swipeable example from GestureHandler Example App.

jsx
import React from 'react';
import { Text, StyleSheet } from 'react-native';

import { GestureHandlerRootView } from 'react-native-gesture-handler';
import ReanimatedSwipeable from 'react-native-gesture-handler/ReanimatedSwipeable';
import Reanimated, {
SharedValue,
useAnimatedStyle,
} from 'react-native-reanimated';

function RightAction(prog: SharedValue<number>, drag: SharedValue<number>) {
const styleAnimation = useAnimatedStyle(() => {
console.log('showRightProgress:', prog.value);
console.log('appliedTranslation:', drag.value);

return {
transform: [{ translateX: drag.value + 50 }],
};
});

return (
<Reanimated.View style={styleAnimation}>
<Text style={styles.rightAction}>Text</Text>
</Reanimated.View>
);
}

export default function Example() {
return (
<GestureHandlerRootView>
<ReanimatedSwipeable
containerStyle={styles.swipeable}
friction={2}
enableTrackpadTwoFingerGesture
rightThreshold={40}
renderRightActions={RightAction}>
<Text>Swipe me!</Text>
</ReanimatedSwipeable>
</GestureHandlerRootView>
);
}

const styles = StyleSheet.create({
rightAction: { width: 50, height: 50, backgroundColor: 'purple' },
separator: {
width: '100%',
borderTopWidth: 1,
},
swipeable: {
height: 50,
backgroundColor: 'papayawhip',
alignItems: 'center',
},
});

text
---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Components/Touchables (packages/docs-gesture-handler/versioned_docs/version-2.x/components/touchables.md)

---
id: touchables
title: Touchables
sidebar_label: Touchables
---

:::warning
Touchables will be removed in the future version of Gesture Handler. Use Pressable instead.
:::

Gesture Handler library provides an implementation of RN's touchable components that are based on native buttons and does not rely on JS responder system utilized by RN. Our touchable implementation follows the same API and aims to be a drop-in replacement for touchables available in React Native.

React Native's touchables API can be found here:

- Touchable Native Feedback
- Touchable Highlight
- Touchable Opacity
- Touchable Without Feedback

All major touchable properties (except from pressRetentionOffset) have been adopted and should behave in a similar way as with RN's touchables.

The motivation for using RNGH touchables as a replacement for these imported from React Native is to follow built-in native behavior more closely by utilizing platform native touch system instead of relying on the JS responder system.
These touchables and their feedback behavior are deeply integrated with native
gesture ecosystem and could be connected with other native components (e.g.
ScrollView) and Gesture Handlers easily and in a more predictable way, which
follows native apps' behavior.

Our intention was to make switch for these touchables as simple as possible. In order to use RNGH's touchables the only thing you need to do is to change the library from which you import touchable components.

:::info
Gesture Handler's TouchableOpacity uses native driver for animations by default. If this causes problems for you, you can set
useNativeAnimations prop to false.
:::

Example:

javascript
import {
TouchableNativeFeedback,
TouchableHighlight,
TouchableOpacity,
TouchableWithoutFeedback,
} from 'react-native';
text
has to be replaced with:
javascript
import {
TouchableNativeFeedback,
TouchableHighlight,
TouchableOpacity,
TouchableWithoutFeedback,
} from 'react-native-gesture-handler';
text
For a comparison of both touchable implementations see our touchables example

---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Fundamentals/ Category .Json (packages/docs-gesture-handler/versioned_docs/version-2.x/fundamentals/_category_.json)

{
"label": "Fundamentals",
"position": 1,
"link": {
"type": "generated-index"
}
}

---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Fundamentals/Gesture Composition (packages/docs-gesture-handler/versioned_docs/version-2.x/fundamentals/gesture-composition.md)

---
id: gesture-composition
title: Gesture composition & interactions
sidebar_label: Gesture composition & interactions
sidebar_position: 3
---

Composing gestures is much simpler in RNGH2, you don't need to create a ref for every gesture that depends on another one. Instead you can use Race, Simultaneous and Exclusive methods provided by the Gesture object.

Race

Only one of the provided gestures can become active at the same time. The first gesture to become active will cancel the rest of the gestures. It accepts variable number of arguments.
It is the equivalent to having more than one gesture handler without defining
simultaneousHandlers and waitFor props.

For example, let's say that you have a component that you want to make draggable but you also want to show additional options on long press. Presumably you would not want the component to move after the long press activates. You can accomplish this using Race:

Note: the useSharedValue and useAnimatedStyle are part of react-native-reanimated.

/ Detailed source-code truncated for AI context efficiency. /
text

Simultaneous

All of the provided gestures can activate at the same time. Activation of one will not cancel the other.
It is the equivalent to having some gesture handlers, each with
simultaneousHandlers prop set to the other handlers.

For example, if you want to make a gallery app, you might want user to be able to zoom, rotate and pan around photos. You can do it with Simultaneous:

Note: the useSharedValue and useAnimatedStyle are part of react-native-reanimated.

/ Detailed source-code truncated for AI context efficiency. /
text

Exclusive

Only one of the provided gestures can become active, with the first one having a higher priority than the second one (if both gestures are still possible, the second one will wait for the first one to fail before it activates), second one having a higher priority than the third one, and so on.
It is equivalent to having some gesture handlers where the second one has the
waitFor prop set to the first handler, third one has the waitFor prop set to the first and the second one, and so on.

For example, if you want to make a component that responds to single tap as well as to a double tap, you can accomplish that using Exclusive:

Note: the useSharedValue and useAnimatedStyle are part of react-native-reanimated.
js
import { GestureDetector, Gesture } from 'react-native-gesture-handler';

function App() {
const singleTap = Gesture.Tap().onEnd((_event, success) => {
if (success) {
console.log('single tap!');
}
});
const doubleTap = Gesture.Tap()
.numberOfTaps(2)
.onEnd((_event, success) => {
if (success) {
console.log('double tap!');
}
});

const taps = Gesture.Exclusive(doubleTap, singleTap);

return (
<GestureDetector gesture={taps}>
<Component />
</GestureDetector>
);
}

text

Cross-component interactions

You may have noticed that gesture composition described above requires you to mount all of the composed gestures under a single GestureDetector, effectively attaching them to the same underlying component. You can customize how gestures interact with each other across multiple components in a couple of ways:

requireExternalGestureToFail

requireExternalGestureToFail allows delaying activation of the handler until all handlers passed as arguments to this method fail (or don't begin at all).

For example, you may want to have two nested components, both of them can be tapped by the user to trigger different actions: outer view requires one tap, but the inner one requires 2 taps. If you don't want the first tap on the inner view to activate the outer handler, you must make the outer gesture wait until the inner one fails:

jsx
import React from 'react';
import { View, StyleSheet } from 'react-native';
import {
GestureDetector,
Gesture,
GestureHandlerRootView,
} from 'react-native-gesture-handler';

export default function Example() {
const innerTap = Gesture.Tap()
.numberOfTaps(2)
.onStart(() => {
console.log('inner tap');
});

const outerTap = Gesture.Tap()
.onStart(() => {
console.log('outer tap');
})
.requireExternalGestureToFail(innerTap);

return (
<GestureHandlerRootView style={styles.container}>
<GestureDetector gesture={outerTap}>
<View style={styles.outer}>
<GestureDetector gesture={innerTap}>
<View style={styles.inner} />
</GestureDetector>
</View>
</GestureDetector>
</GestureHandlerRootView>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
outer: {
width: 250,
height: 250,
backgroundColor: 'lightblue',
},
inner: {
width: 100,
height: 100,
backgroundColor: 'blue',
alignSelf: 'center',
},
});

text

blocksExternalGesture

blocksExternalGesture works similarly to requireExternalGestureToFail but the direction of the relation is reversed - instead of being a one-to-many relation, it's many-to-one. It's especially useful for making lists where the ScrollView component needs to wait for every gesture underneath it. All that is required to do is to pass a ref, for example:


/ Detailed source-code truncated for AI context efficiency. /
text

simultaneousWithExternalGesture

simultaneousWithExternalGesture allows gestures across different components to be recognized simultaneously. For example, you may want to have two nested views, both with tap gesture attached. Both of them require one tap, but tapping the inner one should also activate the gesture attached to the outer view:

jsx
import React from 'react';
import { View, StyleSheet } from 'react-native';
import {
GestureDetector,
Gesture,
GestureHandlerRootView,
} from 'react-native-gesture-handler';

export default function Example() {
const innerTap = Gesture.Tap()
.onStart(() => {
console.log('inner tap');
});

const outerTap = Gesture.Tap()
.onStart(() => {
console.log('outer tap');
})
.simultaneousWithExternalGesture(innerTap);

return (
<GestureHandlerRootView style={styles.container}>
<GestureDetector gesture={outerTap}>
<View style={styles.outer}>
<GestureDetector gesture={innerTap}>
<View style={styles.inner} />
</GestureDetector>
</View>
</GestureDetector>
</GestureHandlerRootView>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
outer: {
width: 250,
height: 250,
backgroundColor: 'lightblue',
},
inner: {
width: 100,
height: 100,
backgroundColor: 'blue',
alignSelf: 'center',
},
});

text
---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Fundamentals/Installation (packages/docs-gesture-handler/versioned_docs/version-2.x/fundamentals/installation.md)

---
id: installation
title: Installation
sidebar_position: 2
---

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

Requirements

react-native-gesture-handler supports the three latest minor releases of react-native.

| version | react-native version |
| ------- | ---------------------- |
| 2.28.0+ | 0.79.0+ |
| 2.26.0+ | 0.78.0+ |
| 2.25.0+ | 0.76.0+ |
| 2.24.0+ | 0.75.0+ |
| 2.21.0+ | 0.74.0+ |
| 2.18.0+ | 0.73.0+ |
| 2.16.0+ | 0.68.0+ |
| 2.14.0+ | 0.67.0+ |
| 2.10.0+ | 0.64.0+ |
| 2.0.0+ | 0.63.0+ |

In order to fully utilize the touch events you also need to use react-native-reanimated 2.3.0 or newer.

Setting up react-native-gesture-handler is pretty straightforward:

1. Start with installing the package from npm:

<Tabs groupId="package-managers">
<TabItem value="expo" label="EXPO" default>

bash
npx expo install react-native-gesture-handler
text
</TabItem>
<TabItem value="npm" label="NPM">
bash
npm install react-native-gesture-handler
text
</TabItem>
<TabItem value="yarn" label="YARN">
bash
yarn add react-native-gesture-handler
text
</TabItem>
</Tabs>

2. Wrap your app with GestureHandlerRootView component

jsx
import { GestureHandlerRootView } from 'react-native-gesture-handler';

export default function App() {
return (
<GestureHandlerRootView>
<ActualApp />
</GestureHandlerRootView>
);
}

text
If you don't provide anything to the styles prop, it will default to flex: 1. If you want to customize the styling of the root view, don't forget to also include flex: 1 in the custom style, otherwise your app won't render anything. Keep GestureHandlerRootView as close to the actual root of the app as possible. It's the entry point for all gestures and all gesture relations. The gestures won't be recognized outside of the root view, and relations only work between gestures mounted under the same root view.

If you're unsure if one of your dependencies already renders GestureHandlerRootView on its own, don't worry and add one at the root anyway. In case of nested root views, Gesture Handler will only use the top-most one and ignore the nested ones.

:::tip
If you're using gesture handler in your component library, you may want to wrap your library's code in the
GestureHandlerRootView component. This will avoid extra configuration for the user.
:::

:::tip
If you're having trouble with gestures not working when inside a component provided by a third-party library, even though you've wrapped the entry point with
<GestureHandlerRootView>, you can try adding another <GestureHandlerRootView unstable_forceActive> closer to the place the gestures are defined. This way, you can prevent Android from canceling relevant gestures when one of the native views tries to grab lock for delivering touch events.
:::

3. Platform specific setup

#### Expo development build

When using an Expo development build, run prebuild to update the native code in the ios and android directories.

bash
npx expo prebuild
text
#### Android

Setting up Gesture Handler on Android doesn't require any more steps. Keep in mind that if you want to use gestures in Modals you need to wrap Modal's content with GestureHandlerRootView:

jsx
import { Modal } from 'react-native';
import { GestureHandlerRootView } from 'react-native-gesture-handler';

export function CustomModal({ children, ...rest }) {
return (
<Modal {...rest}>
<GestureHandlerRootView>{children}</GestureHandlerRootView>
</Modal>
);
}

text
##### Kotlin

Gesture Handler on Android is implemented in Kotlin. If you need to set a specific Kotlin version to be used by the library, set the kotlinVersion ext property in android/build.gradle file and RNGH will use that version:

groovy
buildscript {
ext {
kotlinVersion = "1.6.21"
}
}
text
#### iOS

While developing for iOS, make sure to install pods first before running the app:

bash
cd ios && pod install && cd ..
text
#### Web

There is no additional configuration required for the web.

#### With wix/react-native-navigation

If you are using a native navigation library like wix/react-native-navigation you need to make sure that every screen is wrapped with GestureHandlerRootView. This can be done for example at the stage when you register your screens. Here's an example:

js
import { Navigation } from 'react-native-navigation';
import FirstTabScreen from './FirstTabScreen';
import SecondTabScreen from './SecondTabScreen';
import PushedScreen from './PushedScreen';
// register all screens of the app (including internal ones)
export function registerScreens() {
Navigation.registerComponent(
'example.FirstTabScreen',
() => {
return (
<GestureHandlerRootView>
<FirstTabScreen />
</GestureHandlerRootView>
);
},
() => FirstTabScreen
);
Navigation.registerComponent(
'example.SecondTabScreen',
() => {
return (
<GestureHandlerRootView>
<SecondTabScreen />
</GestureHandlerRootView>
);
},
() => SecondTabScreen
);
Navigation.registerComponent(
'example.PushedScreen',
() => {
return (
<GestureHandlerRootView>
<PushedScreen />
</GestureHandlerRootView>
);
},
() => PushedScreen
);
}
text
You can check out this example project to see this kind of set up in action.

Remember that you need to wrap each screen that you use in your app with GestureHandlerRootView as with native navigation libraries each screen maps to a separate root view. It will not be enough to wrap the main screen only.

---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Fundamentals/Introduction (packages/docs-gesture-handler/versioned_docs/version-2.x/fundamentals/introduction.md)

---
id: introduction
title: Introduction
sidebar_label: Introduction
sidebar_position: 1
slug: /
---

Gesture Handler provides a declarative API exposing the native platform's touch and gesture system to React Native. It's designed to be a replacement of React Native's built in touch system called Gesture Responder System. Using native touch handling allows to address the performance limitations of React Native's Gesture Responder System. It also provides more control over the platform's native components that can handle gestures on their own. If you want to learn more, we recommend this talk by Krzysztof Magiera in which he explains issues with the responder system.

The main benefits of using React Native Gesture Handler are:

- A way to use a platform's native touch handling system for recognizing gestures (like pinch, rotation, pan and a few others).
- The ability to define relations between gestures to ensure gestures, and possibly native components, will not conflict with each other.
- Mechanisms to use touchable components that run in native thread and follow platform default behavior; e.g. in the event they are in a scrollable component, turning into pressed state is slightly delayed to prevent it from highlighting when you fling.
- Close integration with
react-native-reanimated to process touch events on the UI thread.
- Support for different input devices like touch screens, pens and mice.
- Ability to include any native component into the Gesture Handler's touch system, making it work alongside your gestures.

:::info
We recommend using Reanimated to implement gesture-driven animations with Gesture Handler. Its more advanced features rely heavily on worklets and the UI runtime provided by Reanimated.
:::

Learning resources

Apps

Gesture Handler Example App – official gesture handler "showcase" app.

Talks and workshops

Declarative future of gestures and animations in React Native by Krzysztof Magiera - talk that explains motivation behind creating gesture handler library. It also presents react-native-reanimated and how and when it can be used with gesture handler.

React Native workshop with Expo team @ReactEurope 2018 by Brent Vatne – great workshop explaining gesture handler in details and presenting a few exercises that will help get you started.

Living in an async world of React Native by Krzysztof Magiera – talk which highlights some issue with the React Native's touch system Gesture Handler aims to address. Also the motivation for building this library is explained.

React Native Touch & Gesture by Krzysztof Magiera - talk explaining JS responder system limitations and points out some of the core features of Gesture Handler.

Contributing

If you are interested in the project and want to contribute or support it in other ways don't hesitate to contact anyone from the team on Twitter or Bluesky (links below)!

All PRs are welcome, but talk to us before you start working on something big.

The easiest way to get started with contributing code is by:

- Reviewing the list of open issues and trying to solve the one that seem approachable to you.
- Updating the documentation whenever you see some information is unclear, missing or out of date.

Code is only one way how you can contribute. You may want to consider replying on issues if you know how to help.

Community

We are very proud of the community that has been build around this package. We really appreciate all your help regardless of whether it is a pull request, issue report, helping others by commenting on existing issues or posting some demo or video tutorial on social media.
If you've build something with this library you'd like to share, please contact us as we'd love to help share it with others.

Gesture Handler Team 🚀

<div className="community-holder-container">

<div className="community-holder-container-item">
<div className="community-imageHolder">
<img src="https://ca.slack-edge.com/T03Q9AMJJ-U02700KC6J1-0c9e18c89e71-512" />
</div>
<div>Jakub Piasecki</div>
<div><a href="https://twitter.com/breskin67">@breskin67</a></div>
<div><a href="https://bsky.app/profile/jpiasecki.com">@jpiasecki.com</a></div>
</div>

<div className="community-holder-container-item">
<div className="community-imageHolder">
<img src="https://ca.slack-edge.com/T03Q9AMJJ-U03N3HU2C0M-60a31c54a7d5-512" />
</div>
<div>Michał Bert</div>
<div><a href="https://x.com/Michal3870">@Michal3870</a></div>
</div>

<div className="community-holder-container-item">
<div className="community-imageHolder">
<img src="https://ca.slack-edge.com/T03Q9AMJJ-U06MQFHEY3V-6535cb89fc75-192" />
</div>
<div>Ignacy Łątka</div>
<div><a href="https://x.com/latekvo">@latekvo</a></div>
</div>

<div className="community-holder-container-item">
<div className="community-imageHolder">
<img src="https://ca.slack-edge.com/T03Q9AMJJ-U0F40CATS-d0a2e7559a1b-512" />
</div>
<div>Krzysztof Magiera</div>
<div><a href="https://twitter.com/kzzzf">@kzzzf</a></div>
<div><a href="https://bsky.app/profile/kzzzf.bsky.social">@kzzzf.bsky.social</a></div>
</div>

</div>

Sponsors

We really appreciate our sponsors! Thanks to them we can develop our library and make the react-native world a better place. Special thanks for:

<div className="community-holder-container">

<div className="community-holder-container-item">
<a href="https://expo.dev">
<div className="community-imageHolder">
<img className="community-imageHolder" src="https://avatars2.githubusercontent.com/u/12504344?v=3&s=100" />
</div>
<div>Expo</div>
</a>
</div>

</div>

---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Fundamentals/States Events (packages/docs-gesture-handler/versioned_docs/version-2.x/fundamentals/states-events.mdx)

---
id: states-events
title: Gesture states & events
sidebar_label: Gesture states & events
sidebar_position: 4
---

Every gesture can be treated as "state machine".
At any given time, each handler instance has an assigned state that can change when new touch events occur or can be forced to change by the touch system in certain circumstances.

A gesture can be in one of the six possible states:

- #### UNDETERMINED

This is the initial state of each gesture recognizer and it goes into this state after it's done recognizing a gesture.

- #### FAILED

A gesture recognizer received some touches but for some reason didn't recognize them. For example, if a finger travels more distance than a defined maxDist property allows, then the gesture won't become active but will fail instead. Afterwards, its state will be reset to UNDETERMINED.

- #### BEGAN

Gesture recognizer has started receiving touch stream but hasn't yet received enough data to either fail or activate.

- #### CANCELLED

The gesture recognizer has received a signal (possibly new touches or a command from the touch system controller) resulting in the cancellation of a continuous gesture. The gesture's state will become CANCELLED until it is finally reset to the initial state, UNDETERMINED.

- #### ACTIVE

Recognizer has recognized a gesture. It will become and stay in the ACTIVE state until the gesture finishes (e.g. when user lifts the finger) or gets cancelled by the touch system. Under normal circumstances the state will then turn into END. In the case that a gesture is cancelled by the touch system, its state would then become CANCELLED.

- #### END

The gesture recognizer has received touches signalling the end of a gesture. Its state will become END until it is reset to UNDETERMINED.

State flows

The most typical flow of state is when a gesture picks up on an initial touch event, then recognizes it, then acknowledges its ending and resets itself back to the initial state.

The flow looks as follows:

import GestureStateFlowExample from '@site/src/examples/GestureStateFlowExampleLegacy';

<InteractiveExample
component={<GestureStateFlowExample />}
label="Drag or long-press the circle"
larger={true}
/>

Events

There are three types of events in RNGH2: StateChangeEvent, GestureEvent and PointerEvent. The StateChangeEvent is sent every time a gesture moves to a different state, while GestureEvent is sent every time a gesture is updated. The first two carry a gesture-specific data and a state property, indicating the current state of the gesture. StateChangeEvent also carries a oldState property indicating the previous state of the gesture. PointerEvent carries information about raw touch events, like touching the screen or moving the finger. These events are handled internally before they are passed along to the correct callbacks:

onBegin

Is called when a gesture transitions to the BEGAN state.

onStart

Is called when a gesture transitions to the ACTIVE state.

onEnd

Is called when a gesture transitions from the ACTIVE state to the END, FAILED, or CANCELLED state. If the gesture transitions to the END state, the success argument is set to true otherwise it is set to false.

onFinalize

Is called when a gesture transitions to the END, FAILED, or CANCELLED state. If the gesture transitions to the END state, the success argument is set to true otherwise it is set to false. If the gesture transitions from the ACTIVE state, it will be called after onEnd.

onUpdate

Is called every time a gesture is updated while it is in the ACTIVE state.

onPointerDown

Is called when new pointers are placed on the screen. It may carry information about more than one pointer because the events are batched.

onPointerMove

Is called when pointers are moved on the screen. It may carry information about more than one pointer because the events are batched.

onPointerUp

Is called when pointers are lifted from the screen. It may carry information about more than one pointer because the events are batched.

onPointerCancelled

Is called when there will be no more information about this pointer. It may be called because the gesture has ended or was interrupted. It may carry information about more than one pointer because the events are batched.

---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gesture Handlers/ Category .Json (packages/docs-gesture-handler/versioned_docs/version-2.x/gesture-handlers/_category_.json)

{
"label": "Gesture handlers (legacy)",
"position": 6,
"link": {
"type": "generated-index"
}
}

---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gesture Handlers/About Handlers (packages/docs-gesture-handler/versioned_docs/version-2.x/gesture-handlers/about-handlers.md)

---
id: about-handlers
title: About Gesture Handlers
sidebar_label: About Gesture Handlers
sidebar_position: 1
---

:::warning
The old API will be removed in the future version of Gesture Handler. Please migrate to gestures API instead. Check out our upgrading guide for more information.
:::

Gesture handlers are the core building blocks of this library.
We use this term to describe elements of the native touch system that the library allows us to instantiate and control from JavaScript using React's Component interface.

Each handler type is capable of recognizing one type of gesture (pan, pinch, etc.) and provides gesture-specific information via events (translation, scale, etc.).

Handlers analyze the touch stream synchronously in the UI thread. This allows for uninterrupted interactions even when the JavaScript thread is blocked.

Each handler works as an isolated state machine. It takes a touch stream as an input and based on it, it can flip between states.
When a gesture starts, based on the position where the finger was placed, a set of handlers that may be interested in recognizing the gesture is selected.
All the touch events (touch down, move, up, or when other fingers are placed or lifted) are delivered to all of the handlers selected initially.
When one gesture becomes active, it cancels all the other gestures (read more about how to influence this process in "Cross handler interactions" section).

Gesture handler components do not instantiate a native view in the view hierarchy. Instead, they are kept in the library's own registry and are only connected to native views. When using any of the gesture handler components, it is important for it to have a native view rendered as a child.
Since handler components don't have corresponding views in the hierarchy, the events registered with them are actually hooked into the underlying view.

Available gesture handlers

Currently, the library provides the following list of gestures. Their parameters and attributes they provide to gesture events are documented under each gesture page:

- PanGestureHandler
-
TapGestureHandler
-
LongPressGestureHandler
-
RotationGestureHandler
-
FlingGestureHandler
-
PinchGestureHandler
-
ForceTouchGestureHandler

Discrete vs continuous

We distinguish between two types of gestures: discrete and continuous.

Continuous gesture handlers can be active for a long period of time and will generate a stream of gesture events until the gesture is over.
An example of a continuous handler is
PanGestureHandler that once activated, will start providing updates about translation and other properties.

On the other hand, discrete gesture handlers once activated will not stay in the active state but will end immediately.
LongPressGestureHandler is a discrete handler, as it only detects if the finger is placed for a sufficiently long period of time, it does not track finger movements (as that's the responsibility of PanGestureHandler).

Keep in mind that onGestureEvent is only generated by continuous gesture handlers and shouldn't be used in the TapGestureHandler and other discrete handlers.

Nesting handlers

Handler components can be nested. In any case, it is recommended that the innermost handler renders a native view component. There are some limitations that apply when using useNativeDriver flag. An example of nested handlers:

js
class Multitap extends Component {
render() {
return (
<LongPressGestureHandler
onHandlerStateChange={this._onLongpress}
minDurationMs={800}>
<TapGestureHandler
onHandlerStateChange={this._onSingleTap}
waitFor={this.doubleTapRef}>
<TapGestureHandler
ref={this.doubleTapRef}
onHandlerStateChange={this._onDoubleTap}
numberOfTaps={2}>
<View style={styles.box} />
</TapGestureHandler>
</TapGestureHandler>
</LongPressGestureHandler>
);
}
}
text

Using native components

Gesture handler library exposes a set of components normally available in React Native that are wrapped in NativeViewGestureHandler.
Here is a list of exposed components:

- ScrollView
-
FlatList
-
Switch
-
TextInput
-
DrawerLayoutAndroid (Android only)

If you want to use other handlers or buttons nested in a ScrollView, use the waitFor property to define interaction between a handler and ScrollView.

Events with useNativeDriver

Because handlers do not instantiate native views but instead hook up to their child views, directly nesting two gesture handlers using Animated.event is not currently supported.
To workaround this limitation we recommend placing an
<Animated.View> component in between the handlers.

Instead of doing:

js
const PanAndRotate = () => (
<PanGestureHandler onGestureEvent={Animated.event({ ... }, { useNativeDriver: true })}>
<RotationGestureHandler onGestureEvent={Animated.event({ ... }, { useNativeDriver: true })}>
<Animated.View style={animatedStyles}/>
</RotationGestureHandler>
</PanGestureHandler>
);
text
Place an <Animated.View> in between the handlers:
js
const PanAndRotate = () => (
<PanGestureHandler onGestureEvent={Animated.event({ ... }, { useNativeDriver: true })}>
<Animated.View>
<RotationGestureHandler onGestureEvent={Animated.event({ ... }, { useNativeDriver: true })}>
<Animated.View style={animatedStyles}/>
</RotationGestureHandler>
</Animated.View>
</PanGestureHandler>
);
text
Another consequence of handlers depending on their native child components is that when using a useNativeDriver flag with an Animated.event, the child component must be wrapped by an Animated.API, e.g. <Animated.View> instead of just a <View>:
js
class Draggable extends Component {
render() {
return (
<PanGestureHandler onGestureEvent={Animated.event({ ... }, { useNativeDriver: true })}>
<Animated.View style={animatedStyles} /> {/ <-- NEEDS TO BE Animated.View /}
</PanGestureHandler>
);
}
};
text
---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gesture Handlers/Common Gh (packages/docs-gesture-handler/versioned_docs/version-2.x/gesture-handlers/common-gh.md)

---
id: common-gh
title: Common handler properties
sidebar_label: Common handler properties
sidebar_position: 4
---

:::warning
The old API will be removed in the future version of Gesture Handler. Please migrate to gestures API instead. Check out our upgrading guide for more information.
:::

This page covers the common set of properties all gesture handler components expose.

Units

All handler component properties and event attributes that represent onscreen dimensions are expressed in screen density independent units we refer to as "points".
These are the units commonly used in React Native ecosystem (e.g. in the layout system).
They do not map directly to physical pixels but instead to iOS's points and to dp units on Android.

Properties

This section describes properties that can be used with all gesture handler components:

enabled

Accepts a boolean value.
Indicates whether the given handler should be analyzing stream of touch events or not.
When set to
false we can be sure that the handler's state will never become ACTIVE.
If the value gets updated while the handler has already started recognizing a gesture, then the handler's state will immediately change to
FAILED or CANCELLED (depending on its current state).
Default value is
true.

shouldCancelWhenOutside

Accepts a boolean value.
When
true the handler will cancel or fail recognition (depending on its current state) whenever the finger leaves the area of the connected view.
Default value of this property is different depending on the handler type.
Most handlers'
shouldCancelWhenOutside property defaults to false except for the LongPressGestureHandler and TapGestureHandler which default to true.

cancelsTouchesInView (iOS only)

Accepts a boolean value.
When
true, the handler will cancel touches for native UI components (UIButton, UISwitch, etc) it's attached to when it becomes ACTIVE.
Default value is
true.

simultaneousHandlers

Accepts a react ref object or an array of refs to other handler components (refs should be created using React.createRef()). When set, the handler will be allowed to activate even if one or more of the handlers provided by their refs are in an ACTIVE state. It will also prevent the provided handlers from cancelling the current handler when they activate. Read more in the cross handler interaction section.

waitFor

Accepts a react ref object or an array of refs to other handler components (refs should be created using React.createRef()). When set, the handler will not activate as long as the handlers provided by their refs are in the BEGAN state. Read more in the cross handler interaction section.

hitSlop

This parameter enables control over what part of the connected view area can be used to begin recognizing the gesture.
When a negative number is provided the bounds of the view will reduce the area by the given number of points in each of the sides evenly.

Instead, you can pass an object to specify how each boundary side should be reduced by providing a different number of points for left, right, top or bottom sides.
You can alternatively provide
horizontal or vertical instead of specifying directly left, right or top and bottom.
Finally, the object can also take
width and height attributes.
When
width is set it is only allowed to specify one of the sides right or left.
Similarly when
height is provided only top or bottom can be set.
Specifying
width or height is useful if we only want the gesture to activate on the edge of the view. In which case for example we can set left: 0 and width: 20 which would make it possible for the gesture to be recognized when started no more than 20 points from the left edge.

IMPORTANT: Note that this parameter is primarily designed to reduce the area where gesture can activate. Hence it is only supported for all the values (except width and height) to be non positive (0 or lower). Although on Android it is supported for the values to also be positive and therefore allow to expand beyond view bounds but not further than the parent view bounds. To achieve this effect on both platforms you can use React Native's View hitSlop property.

userSelect (Web only)

This parameter allows to specify which userSelect property should be applied to underlying view. Possible values are "none" | "auto" | "text". Default value is set to "none".

activeCursor (Web only)

This parameter allows to specify which cursor should be used when gesture activates. Supports all CSS cursor values (e.g. "grab", "zoom-in"). Default value is set to "auto".

onGestureEvent

Takes a callback that is going to be triggered for each subsequent touch event while the handler is in an ACTIVE state. Event payload depends on the particular handler type. Common set of event data attributes is documented below and handler specific attributes are documented on the corresponding handler pages. E.g. event payload for PinchGestureHandler contains a scale attribute that represents how the distance between fingers changed since the gesture started.

Instead of a callback Animated.event object can be used. Also Animated events with useNativeDriver flag enabled are fully supported.

onHandlerStateChange

Takes a callback that is going to be triggered when state of the given handler changes.

The event payload contains the same payload as in the case of onGestureEvent including handler specific event attributes some handlers may provide.

In addition onHandlerStateChange event payload contains oldState attribute which represents the state of the handler right before the change.

Instead of a callback Animated.event object can be used. Also Animated events with useNativeDriver flag enabled are fully supported.

Event data

This section describes the attributes of event object being provided to onGestureEvent and onHandlerStateChange callbacks:

state

Current state of the handler. Expressed as one of the constants exported under State object by the library. Refer to the section about handler state to learn more about how to use it.

numberOfPointers

Represents the number of pointers (fingers) currently placed on the screen.

---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gesture Handlers/Create Native Wrapper (packages/docs-gesture-handler/versioned_docs/version-2.x/gesture-handlers/create-native-wrapper.md)

---
id: create-native-wrapper
title: createNativeWrapper
sidebar_label: createNativeWrapper()
sidebar_position: 13
---

:::warning
The old API will be removed in the future version of Gesture Handler. Please migrate to gestures API instead. Check out our upgrading guide for more information.
:::

Creates provided component with NativeViewGestureHandler, allowing it to be part of RNGH's
gesture system.

Arguments

Component

The component we want to wrap.

config

Config is an object with properties that can be used on NativeViewGestureHandler

Returns

Wrapped component.

---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gesture Handlers/Fling Gh (packages/docs-gesture-handler/versioned_docs/version-2.x/gesture-handlers/fling-gh.md)

---
id: fling-gh
title: FlingGestureHandler
sidebar_label: Fling
sidebar_position: 9
---

:::warning
The old API will be removed in the future version of Gesture Handler. Please migrate to gestures API instead. Check out our upgrading guide for more information.
:::

A discrete gesture handler that activates when the movement is sufficiently long and fast.
Handler gets ACTIVE when movement is sufficiently long and it does not take too much time.
When handler gets activated it will turn into END state when finger is released.
The handler will fail to recognize if the finger is lifted before being activated.
The handler is implemented using UISwipeGestureRecognizer on iOS and from scratch on Android.

Properties

See set of properties inherited from base handler class. Below is a list of properties specific to FlingGestureHandler component:

direction

Expresses the allowed direction of movement. It's possible to pass one or many directions in one parameter:

js
direction={Directions.RIGHT | Directions.LEFT}
text
or
js
direction={Directions.DOWN}
text

numberOfPointers

Determines the exact number of pointers required to handle the fling gesture.

Event data

See set of event attributes from base handler class. Below is a list of gesture event attributes specific to FlingGestureHandler:

x

X coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the handler. Expressed in point units.

y

Y coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the handler. Expressed in point units.

absoluteX

X coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the window. The value is expressed in point units. It is recommended to use it instead of x in cases when the original view can be transformed as an effect of the gesture.

absoluteY

Y coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the window. The value is expressed in point units. It is recommended to use it instead of y in cases when the original view can be transformed as an effect of the gesture.

Example

See the fling example from Gesture Handler Example App.

js
const LongPressButton = () => (
<FlingGestureHandler
direction={Directions.RIGHT | Directions.LEFT}
onHandlerStateChange={({ nativeEvent }) => {
if (nativeEvent.state === State.ACTIVE) {
Alert.alert("I'm flinged!");
}
}}>
<View style={styles.box} />
</FlingGestureHandler>
);
text
---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gesture Handlers/Force Gh (packages/docs-gesture-handler/versioned_docs/version-2.x/gesture-handlers/force-gh.md)

---
id: force-gh
title: ForceTouchGestureHandler (iOS only)
sidebar_label: Force touch
sidebar_position: 11
---

:::warning
The old API will be removed in the future version of Gesture Handler. Please migrate to gestures API instead. Check out our upgrading guide for more information.
:::

A continuous gesture handler that recognizes force of a touch. It allows for tracking pressure of touch on some iOS devices.
The handler activates when pressure of touch is greater or equal to
minForce. It fails if pressure is greater than maxForce.
Gesture callback can be used for continuous tracking of the touch pressure. It provides information for one finger (the first one).

At the beginning of the gesture, the pressure factor is 0.0. As the pressure increases, the pressure factor increases proportionally. The maximum pressure is 1.0.

The handler is implemented using custom UIGestureRecognizer on iOS. There's no implementation provided on Android and it simply renders children without any wrappers.
Since this behaviour is only provided on some iOS devices, this handler should not be used for defining any crucial behaviors. Use it only as an additional improvement and make all features to be accessed without this handler as well.

Properties

See set of properties inherited from base handler class. Below is a list of properties specific to ForceTouchGestureHandler component:

minForce

A minimal pressure that is required before handler can activate. Should be a value from range [0.0, 1.0]. Default is 0.2.

maxForce

A maximal pressure that could be applied for handler. If the pressure is greater, handler fails. Should be a value from range [0.0, 1.0].

feedbackOnActivation

Boolean value defining if haptic feedback has to be performed on activation.

Event data

See set of event attributes from base handler class. Below is a list of gesture event attributes specific to ForceTouchGestureHandler:

force

The pressure of a touch.

Static method

forceTouchAvailable

You may check if it's possible to use ForceTouchGestureHandler with ForceTouchGestureHandler.forceTouchAvailable

Example

See the force touch handler example from Gesture Handler Example App.

js
<ForceTouchGestureHandler
minForce={0}
onGestureEvent={this._onGestureEvent}
onHandlerStateChange={this._onHandlerStateChange}>
<Animated.View
style={[
styles.box,
{ transform: [{ scale: Animated.add(1, this.force) }] },
]}
/>
</ForceTouchGestureHandler>
text
---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gesture Handlers/Interactions (packages/docs-gesture-handler/versioned_docs/version-2.x/gesture-handlers/interactions.md)

---
id: interactions
title: Cross handler interactions
sidebar_label: Cross handler interactions
sidebar_position: 3
---

:::warning
The old API will be removed in the future version of Gesture Handler. Please migrate to gestures API instead. Check out our upgrading guide for more information.
:::

Gesture handlers can "communicate" with each other to support complex gestures and control how they _activate_ in certain scenarios.

There are two means of achieving that described in the sections below.
In each case, it is necessary to provide a reference of one handler as a property to the other.
Gesture handler relies on ref objects created using
React.createRef(), introduced in React 16.3.

Simultaneous recognition

By default, only one gesture handler is allowed to be in the ACTIVE state.
So when a gesture handler recognizes a gesture it cancels all other handlers in the
BEGAN state and prevents any new handlers from receiving a stream of touch events as long as it remains ACTIVE.

This behavior can be altered using the simultaneousHandlers property (available for all types of handlers).
This property accepts a ref or an array of refs to other handlers.
Handlers connected in this way will be allowed to remain in the
ACTIVE state at the same time.

Use cases

Simultaneous recognition needs to be used when implementing a photo preview component that supports zooming (scaling) the photo, rotating and panning it while zoomed in.
In this case we would use a
PinchGestureHandler, RotationGestureHandler and PanGestureHandler that would have to simultaneously recognize gestures.

Example

See the "Scale, rotate & tilt" example from the GestureHandler Example App or view it directly on your phone by visiting our expo demo.

js
class PinchableBox extends React.Component {
// ...take a look on full implementation in an Example app
render() {
const imagePinch = React.createRef();
const imageRotation = React.createRef();
return (
<RotationGestureHandler
ref={imageRotation}
simultaneousHandlers={imagePinch}
onGestureEvent={this._onRotateGestureEvent}
onHandlerStateChange={this._onRotateHandlerStateChange}>
<Animated.View>
<PinchGestureHandler
ref={imagePinch}
simultaneousHandlers={imageRotation}
onGestureEvent={this._onPinchGestureEvent}
onHandlerStateChange={this._onPinchHandlerStateChange}>
<Animated.View style={styles.container} collapsable={false}>
<Animated.Image
style={[
styles.pinchableImage,
{
/ events-related transformations /
},
]}
/>
</Animated.View>
</PinchGestureHandler>
</Animated.View>
</RotationGestureHandler>
);
}
}
text

Awaiting other handlers

Use cases

A good example where awaiting is necessary is when we want to have single and double tap handlers registered for one view (a button).
In such a case we need to make single tap handler await a double tap.
Otherwise if we try to perform a double tap the single tap handler will fire just after we hit the button for the first time, consequently cancelling the double tap handler.

Example

See the "Multitap" example from GestureHandler Example App or view it directly on your phone by visiting our expo demo.

js
const doubleTap = React.createRef();
const PressBox = () => (
<TapGestureHandler
onHandlerStateChange={({ nativeEvent }) =>
nativeEvent.state === State.ACTIVE && Alert.alert('Single tap!')
}
waitFor={doubleTap}>
<TapGestureHandler
ref={doubleTap}
onHandlerStateChange={({ nativeEvent }) =>
nativeEvent.state === State.ACTIVE && Alert.alert("You're so fast")
}
numberOfTaps={2}>
<View style={styles.box} />
</TapGestureHandler>
</TapGestureHandler>
);
text
---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gesture Handlers/Longpress Gh (packages/docs-gesture-handler/versioned_docs/version-2.x/gesture-handlers/longpress-gh.md)

---
id: longpress-gh
title: LongPressGestureHandler
sidebar_label: Long press
sidebar_position: 7
---

:::warning
The old API will be removed in the future version of Gesture Handler. Please migrate to gestures API instead. Check out our upgrading guide for more information.
:::

A discrete gesture handler that activates when the corresponding view is pressed for a sufficiently long time.
This handler's state will turn into END immediately after the finger is released.
The handler will fail to recognize a touch event if the finger is lifted before the minimum required time or if the finger is moved further than the allowable distance.

The handler is implemented using UILongPressGestureRecognizer on iOS and LongPressGestureHandler on Android.

Properties

See set of properties inherited from base handler class. Below is a list of properties specific to the LongPressGestureHandler component:

minDurationMs

Minimum time, expressed in milliseconds, that a finger must remain pressed on the corresponding view. The default value is 500.

maxDist

Maximum distance, expressed in points, that defines how far the finger is allowed to travel during a long press gesture. If the finger travels further than the defined distance and the handler hasn't yet activated, it will fail to recognize the gesture. The default value is 10.

Event data

See set of event attributes from base handler class. Below is a list of gesture event attributes specific to the LongPressGestureHandler component:

x

X coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the handler.

y

Y coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the handler.

absoluteX

X coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the window. It is recommended to use absoluteX instead of x in cases when the view attached to the handler can be transformed as an effect of the gesture.

absoluteY

Y coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the window. It is recommended to use absoluteY instead of y in cases when the view attached to the handler can be transformed as an effect of the gesture.

duration

Duration of the long press (time since the start of the event), expressed in milliseconds.

Example

See the multitap example from GestureHandler Example App.

js
const LongPressButton = () => (
<LongPressGestureHandler
onHandlerStateChange={({ nativeEvent }) => {
if (nativeEvent.state === State.ACTIVE) {
Alert.alert("I'm being pressed for so long");
}
}}
minDurationMs={800}>
<View style={styles.box} />
</LongPressGestureHandler>
);
text
---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gesture Handlers/Nativeview Gh (packages/docs-gesture-handler/versioned_docs/version-2.x/gesture-handlers/nativeview-gh.md)

---
id: nativeview-gh
title: NativeViewGestureHandler
sidebar_label: NativeView
sidebar_position: 12
---

:::warning
The old API will be removed in the future version of Gesture Handler. Please migrate to gestures API instead. Check out our upgrading guide for more information.
:::

A gesture handler that allows other touch handling components to participate in
RNGH's gesture system.

Used by createNativeWrapper().

Properties

See set of properties inherited from base handler class. Below is a list of properties specific to NativeViewGestureHandler component:

shouldActivateOnStart (Android only)

When true, underlying handler will activate unconditionally when in BEGAN or UNDETERMINED state.

disallowInterruption

When true, cancels all other gesture handlers when this NativeViewGestureHandler receives an ACTIVE state event.

---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gesture Handlers/Pan Gh (packages/docs-gesture-handler/versioned_docs/version-2.x/gesture-handlers/pan-gh.md)

---
id: pan-gh
title: PanGestureHandler
sidebar_label: Pan
sidebar_position: 5
---

:::warning
The old API will be removed in the future version of Gesture Handler. Please migrate to gestures API instead. Check out our upgrading guide for more information.
:::

A continuous gesture handler that can recognize a panning (dragging) gesture and track its movement.

The handler activates when a finger is placed on the screen and moved some initial distance.

Configurations such as a minimum initial distance, specific vertical or horizontal pan detection and number of fingers required for activation (allowing for multifinger swipes) may be specified.

Gesture callback can be used for continuous tracking of the pan gesture. It provides information about the gesture such as its XY translation from the starting point as well as its instantaneous velocity.

The handler is implemented using UIPanGestureRecognizer on iOS and PanGestureHandler on Android.

Custom activation criteria

The PanGestureHandler component exposes a number of properties that can be used to customize the criteria under which a handler will activate or fail when recognizing a gesture.

When more than one of such a property is set, PanGestureHandler expects all criteria to be met for successful recognition and at most one of the criteria to be overstepped to fail recognition.
For example when both
minDeltaX and minDeltaY are set to 20 we expect the finger to travel by 20 points in both the X and Y axis before the handler activates.
Another example would be setting both
maxDeltaX and maxDeltaY to 20 and minDist to 23.
In such a case, if we move a finger along the X-axis by 20 points and along the Y-axis by 0 points, the handler will fail even though the finger is still within the bounds of translation along Y-axis.

Multi touch pan handling

If your app relies on multi touch pan handling this section provides some information about how the default behavior differs between platforms and how (if necessary) it can be unified.

The difference in multi touch pan handling lies in the way translation properties during the event are being calculated.
On iOS the default behavior when more than one finger is placed on the screen is to treat this situation as if only one pointer was placed in the center of mass (average position of all the pointers).
This applies also to many platform native components that handle touch even if not primarily interested in multi touch interactions, like for example the UIScrollView component.

The default behavior for native components like scroll view, pager views or drawers is different and hence gesture handler defaults to that when it comes to pan handling.
The difference is that instead of treating the center of mass of all the fingers placed as a leading pointer it takes the latest placed finger as such.
This behavior can be changed on Android using
avgTouches flag.

Note that on both Android and iOS when the additional finger is placed on the screen that translation prop is not affected even though the position of the pointer being tracked might have changed.
Therefore it is safe to rely on translation most of the time as it only reflects the movement that happens regardless of how many fingers are placed on the screen and if that number changes over time.
If you wish to track the "center of mass" virtual pointer and account for its changes when the number of finger changes you can use relative or absolute position provided in the event (
x and y or absoluteX and absoluteY).

Properties

See set of properties inherited from base handler class. Below is a list of properties specific to PanGestureHandler component:

minDist

Minimum distance the finger (or multiple finger) need to travel before the handler activates. Expressed in points.

minVelocity

Minimum speed the pointer has to reach in order for the handler to activate. Expressed in points per second.

minVelocityX

Minimum speed along X axis the pointer has to reach in order for the handler to activate. Expressed in points per second.

minVelocityY

Minimum speed along Y axis the pointer has to reach in order for the handler to activate. Expressed in points per second.

minPointers

A number of fingers that is required to be placed before handler can activate. Should be an integer greater than or equal to 0.

maxPointers

When the given number of fingers is placed on the screen and handler hasn't yet activated it will fail recognizing the gesture. Should be an integer greater than or equal to 0.

activeOffsetX

Range along X axis (in points) where fingers travels without activation of handler. Moving outside of this range implies activation of handler. Range can be given as an array or a single number.
If range is set as an array, first value must be lower or equal to 0, and the second one higher or equal to 0.
If only one number
p is given a range of (-inf, p) will be used if p is higher or equal to 0 and (-p, inf) otherwise.

activeOffsetY

Range along Y axis (in points) where fingers travels without activation of handler. Moving outside of this range implies activation of handler. Range can be given as an array or a single number.
If range is set as an array, first value must be lower or equal to 0, and the second one higher or equal to 0.
If only one number
p is given a range of (-inf, p) will be used if p is higher or equal to 0 and (-p, inf) otherwise.

failOffsetY

When the finger moves outside this range (in points) along Y axis and handler hasn't yet activated it will fail recognizing the gesture. Range can be given as an array or a single number.
If range is set as an array, first value must be lower or equal to 0, and the second one higher or equal to 0.
If only one number
p is given a range of (-inf, p) will be used if p is higher or equal to 0 and (-p, inf) otherwise.

failOffsetX

When the finger moves outside this range (in points) along X axis and handler hasn't yet activated it will fail recognizing the gesture. Range can be given as an array or a single number.
If range is set as an array, first value must be lower or equal to 0, and the second one higher or equal to 0.
If only one number
p is given a range of (-inf, p) will be used if p is higher or equal to 0 and (-p, inf) otherwise.

maxDeltaX

This method is deprecated but supported for backward compatibility. Instead of using maxDeltaX={N} you can do failOffsetX={[-N, N]}.

When the finger travels the given distance expressed in points along X axis and handler hasn't yet activated it will fail recognizing the gesture.

maxDeltaY

This method is deprecated but supported for backward compatibility. Instead of using maxDeltaY={N} you can do failOffsetY={[-N, N]}.

When the finger travels the given distance expressed in points along Y axis and handler hasn't yet activated it will fail recognizing the gesture.

minOffsetX

This method is deprecated but supported for backward compatibility. Instead of using minOffsetX={N} you can do activeOffsetX={N}.

Minimum distance along X (in points) axis the finger (or multiple finger) need to travel before the handler activates. If set to a lower or equal to 0 value we expect the finger to travel "left" by the given distance. When set to a higher or equal to 0 number the handler will activate on a movement to the "right". If you wish for the movement direction to be ignored use minDeltaX instead.

minOffsetY

This method is deprecated but supported for backward compatibility. Instead of using minOffsetY={N} you can do activeOffsetY={N}.

Minimum distance along Y (in points) axis the finger (or multiple finger) need to travel before the handler activates. If set to a lower or equal to 0 value we expect the finger to travel "up" by the given distance. When set to a higher or equal to 0 number the handler will activate on a movement to the "bottom". If you wish for the movement direction to be ignored use minDeltaY instead.

minDeltaX

This method is deprecated but supported for backward compatibility. Instead of using minDeltaX={N} you can do activeOffsetX={[-N, N]}.

Minimum distance along X (in points) axis the finger (or multiple finger) need to travel (left or right) before the handler activates. Unlike minoffsetx this parameter accepts only non-lower or equal to 0 numbers that represents the distance in point units. If you want for the handler to activate for the movement in one particular direction use minOffsetX instead.

minDeltaY

This method is deprecated but supported for backward compatibility. Instead of using minDeltaY={N} you can do activeOffsetY={[-N, N]}.

Minimum distance along Y (in points) axis the finger (or multiple finger) need to travel (top or bottom) before the handler activates. Unlike minOffsetY this parameter accepts only non-lower or equal to 0 numbers that represents the distance in point units. If you want for the handler to activate for the movement in one particular direction use minOffsetY instead.

avgTouches (Android only)

Android, by default, will calculate translation values based on the position of the leading pointer (the first one that was placed on the screen). This prop allows that behavior to be changed to the one that is default on iOS - the averaged position of all active pointers will be used to calculate the translation values.

enableTrackpadTwoFingerGesture (iOS only)

Enables two-finger gestures on supported devices, for example iPads with trackpads. If not enabled the gesture will require click + drag, with enableTrackpadTwoFingerGesture swiping with two fingers will also trigger the gesture.

Event data

See set of event attributes from base handler class. Below is a list of gesture event attributes specific to PanGestureHandler:

translationX

Translation of the pan gesture along X axis accumulated over the time of the gesture. The value is expressed in the point units.

translationY

Translation of the pan gesture along Y axis accumulated over the time of the gesture. The value is expressed in the point units.

velocityX

Velocity of the pan gesture along the X axis in the current moment. The value is expressed in point units per second.

velocityY

Velocity of the pan gesture along the Y axis in the current moment. The value is expressed in point units per second.

x

X coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the handler. Expressed in point units.

y

Y coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the handler. Expressed in point units.

absoluteX

X coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the window. The value is expressed in point units. It is recommended to use it instead of x in cases when the original view can be transformed as an effect of the gesture.

absoluteY

Y coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the window. The value is expressed in point units. It is recommended to use it instead of y in cases when the original view can be transformed as an effect of the gesture.

Example

See the draggable example from Gesture Handler Example App.

js
import React, { Component } from 'react';
import { Animated, Dimensions } from 'react-native';
import {
GestureHandlerRootView,
PanGestureHandler,
} from 'react-native-gesture-handler';

const { width } = Dimensions.get('screen');
const circleRadius = 30;

class Circle extends Component {
_touchX = new Animated.Value(width / 2 - circleRadius);

_onPanGestureEvent = Animated.event([{ nativeEvent: { x: this._touchX } }], {
useNativeDriver: true,
});

render() {
return (
<GestureHandlerRootView>
<PanGestureHandler onGestureEvent={this._onPanGestureEvent}>
<Animated.View
style={{
height: 150,
justifyContent: 'center',
}}>
<Animated.View
style={[
{
backgroundColor: '#42a5f5',
borderRadius: circleRadius,
height: circleRadius * 2,
width: circleRadius * 2,
},
{
transform: [
{
translateX: Animated.add(
this._touchX,
new Animated.Value(-circleRadius)
),
},
],
},
]}
/>
</Animated.View>
</PanGestureHandler>
</GestureHandlerRootView>
);
}
}

export default function App() {
return <Circle />;
}

text
---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gesture Handlers/Pinch Gh (packages/docs-gesture-handler/versioned_docs/version-2.x/gesture-handlers/pinch-gh.md)

---
id: pinch-gh
title: PinchGestureHandler
sidebar_label: Pinch
sidebar_position: 10
---

:::warning
The old API will be removed in the future version of Gesture Handler. Please migrate to gestures API instead. Check out our upgrading guide for more information.
:::

A continuous gesture handler that recognizes pinch gesture. It allows for tracking the distance between two fingers and use that information to scale or zoom your content.
The handler activates when fingers are placed on the screen and change their position.
Gesture callback can be used for continuous tracking of the pinch gesture. It provides information about velocity, anchor (focal) point of gesture and scale.

The distance between the fingers is reported as a scale factor. At the beginning of the gesture, the scale factor is 1.0. As the distance between the two fingers increases, the scale factor increases proportionally.
Similarly, the scale factor decreases as the distance between the fingers decreases.
Pinch gestures are used most commonly to change the size of objects or content onscreen.
For example, map views use pinch gestures to change the zoom level of the map.

The handler is implemented using UIPinchGestureRecognizer on iOS and from scratch on Android.

Properties

Properties provided to PinchGestureHandler do not extend common set of properties from base handler class.

Event data

See set of event attributes from base handler class. Below is a list of gesture event attributes specific to PinchGestureHandler:

scale

The scale factor relative to the points of the two touches in screen coordinates.

velocity

Velocity of the pinch gesture at the current moment. The value is expressed in scale factor per second.

focalX

Position expressed in points along X axis of center anchor point of the gesture.

focalY

Position expressed in points along Y axis of center anchor point of the gesture.

Example

See the scale and rotation example from Gesture Handler Example App.

js
export class PinchableBox extends React.Component {
_baseScale = new Animated.Value(1);
_pinchScale = new Animated.Value(1);
_scale = Animated.multiply(this._baseScale, this._pinchScale);
_lastScale = 1;
_onPinchGestureEvent = Animated.event(
[{ nativeEvent: { scale: this._pinchScale } }],
{ useNativeDriver: USE_NATIVE_DRIVER }
);

_onPinchHandlerStateChange = (event) => {
if (event.nativeEvent.oldState === State.ACTIVE) {
this._lastScale *= event.nativeEvent.scale;
this._baseScale.setValue(this._lastScale);
this._pinchScale.setValue(1);
}
};

render() {
return (
<PinchGestureHandler
onGestureEvent={this._onPinchGestureEvent}
onHandlerStateChange={this._onPinchHandlerStateChange}>
<View style={styles.container} collapsable={false}>
<Animated.Image
style={[
styles.pinchableImage,
{
transform: [{ perspective: 200 }, { scale: this._scale }],
},
]}
/>
</View>
</PinchGestureHandler>
);
}
}

text
---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gesture Handlers/Rotation Gh (packages/docs-gesture-handler/versioned_docs/version-2.x/gesture-handlers/rotation-gh.md)

---
id: rotation-gh
title: RotationGestureHandler
sidebar_label: Rotation
sidebar_position: 8
---

:::warning
The old API will be removed in the future version of Gesture Handler. Please migrate to gestures API instead. Check out our upgrading guide for more information.
:::

A continuous gesture handler that can recognize a rotation gesture and track its movement.

The handler activates when fingers are placed on the screen and change position in a proper way.

Gesture callback can be used for continuous tracking of the rotation gesture. It provides information about the gesture such as the amount rotated, the focal point of the rotation (anchor), and its instantaneous velocity.

The handler is implemented using UIRotationGestureRecognizer on iOS and from scratch on Android.

Properties

Properties provided to RotationGestureHandler do not extend common set of properties from base handler class.

Event data

See set of event attributes from base handler class. Below is a list of gesture event attributes specific to RotationGestureHandler:

rotation

Amount rotated, expressed in radians, from the gesture's focal point (anchor).

velocity

Instantaneous velocity, expressed in point units per second, of the gesture.

anchorX

X coordinate, expressed in points, of the gesture's central focal point (anchor).

anchorY

Y coordinate, expressed in points, of the gesture's central focal point (anchor).

Example

See the scale and rotation example from Gesture Handler Example App.

js
class RotableBox extends React.Component {
_rotate = new Animated.Value(0);
_rotateStr = this._rotate.interpolate({
inputRange: [-100, 100],
outputRange: ['-100rad', '100rad'],
});
_lastRotate = 0;
_onRotateGestureEvent = Animated.event(
[{ nativeEvent: { rotation: this._rotate } }],
{ useNativeDriver: USE_NATIVE_DRIVER }
);
_onRotateHandlerStateChange = (event) => {
if (event.nativeEvent.oldState === State.ACTIVE) {
this._lastRotate += event.nativeEvent.rotation;
this._rotate.setOffset(this._lastRotate);
this._rotate.setValue(0);
}
};
render() {
return (
<RotationGestureHandler
onGestureEvent={this._onRotateGestureEvent}
onHandlerStateChange={this._onRotateHandlerStateChange}>
<Animated.Image
style={[
styles.pinchableImage,
{
transform: [{ perspective: 200 }, { rotate: this._rotateStr }],
},
]}
/>
</RotationGestureHandler>
);
}
}
text
---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gesture Handlers/Tap Gh (packages/docs-gesture-handler/versioned_docs/version-2.x/gesture-handlers/tap-gh.md)

---
id: tap-gh
title: TapGestureHandler
sidebar_label: Tap
sidebar_position: 6
---

:::warning
The old API will be removed in the future version of Gesture Handler. Please migrate to gestures API instead. Check out our upgrading guide for more information.
:::

A discrete gesture handler that recognizes one or many taps.

Tap gestures detect one or more fingers briefly touching the screen.
The fingers involved in these gestures must not move significantly from their initial touch positions.
The required number of taps and allowed distance from initial position may be configured.
For example, you might configure tap gesture recognizers to detect single taps, double taps, or triple taps.

In order for a handler to activate, specified gesture requirements such as minPointers, numberOfTaps, maxDist, maxDurationMs, and maxDelayMs (explained below) must be met. Immediately after the handler activates, it will END.

Properties

See set of properties inherited from base handler class. Below is a list of properties specific to the TapGestureHandler component:

minPointers

Minimum number of pointers (fingers) required to be placed before the handler activates. Should be a positive integer. The default value is 1.

maxDurationMs

Maximum time, expressed in milliseconds, that defines how fast a finger must be released after a touch. The default value is 500.

maxDelayMs

Maximum time, expressed in milliseconds, that can pass before the next tap — if many taps are required. The default value is 500.

numberOfTaps

Number of tap gestures required to activate the handler. The default value is 1.

maxDeltaX

Maximum distance, expressed in points, that defines how far the finger is allowed to travel along the X axis during a tap gesture. If the finger travels further than the defined distance along the X axis and the handler hasn't yet activated, it will fail to recognize the gesture.

maxDeltaY

Maximum distance, expressed in points, that defines how far the finger is allowed to travel along the Y axis during a tap gesture. If the finger travels further than the defined distance along the Y axis and the handler hasn't yet activated, it will fail to recognize the gesture.

maxDist

Maximum distance, expressed in points, that defines how far the finger is allowed to travel during a tap gesture. If the finger travels further than the defined distance and the handler hasn't yet activated, it will fail to recognize the gesture.

Event data

See set of event attributes from base handler class. Below is a list of gesture event attributes specific to the TapGestureHandler component:

x

X coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the handler.

y

Y coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the handler.

absoluteX

X coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the window. It is recommended to use absoluteX instead of x in cases when the view attached to the handler can be transformed as an effect of the gesture.

absoluteY

Y coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the window. It is recommended to use absoluteY instead of y in cases when the view attached to the handler can be transformed as an effect of the gesture.

Example

See the multitap example from GestureHandler Example App.

js
export class PressBox extends Component {
doubleTapRef = React.createRef();
render() {
return (
<TapGestureHandler
onHandlerStateChange={this._onSingleTap}
waitFor={this.doubleTapRef}>
<TapGestureHandler ref={this.doubleTapRef} numberOfTaps={2}>
<View style={styles.box} />
</TapGestureHandler>
</TapGestureHandler>
);
}
}
text
---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gestures/ Category .Json (packages/docs-gesture-handler/versioned_docs/version-2.x/gestures/_category_.json)

{
"label": "Gestures",
"position": 3,
"link": {
"type": "generated-index"
}
}

---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gestures/Composed Gestures (packages/docs-gesture-handler/versioned_docs/version-2.x/gestures/composed-gestures.md)

---
id: composed-gestures
title: Composed gestures
sidebar_label: Composed gestures
sidebar_position: 13
---

Composed gestures (Race, Simultaneous, Exclusive) provide a simple way of building relations between gestures. See Gesture Composition for more details.

Reference

jsx
import { GestureDetector, Gesture } from 'react-native-gesture-handler';

function App() {
const pan = Gesture.Pan();
const longPress = Gesture.LongPress();

// highlight-next-line
const composed = Gesture.Race(pan, longPress);

return (
<GestureDetector gesture={composed}>
<Animated.View />
</GestureDetector>
);
}

text
---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gestures/Fling Gesture (packages/docs-gesture-handler/versioned_docs/version-2.x/gestures/fling-gesture.md)

---
id: fling-gesture
title: Fling gesture
sidebar_label: Fling gesture
sidebar_position: 8
---

import { webContainer } from '@site/src/utils/getGestureStyles';

import FlingGestureBasic from '../examples/FlingGestureBasic';
import FlingGestureBasicSrc from '!!raw-loader!../examples/FlingGestureBasic';

A discrete gesture that activates when the movement is sufficiently long and fast.

<div className={webContainer}>
<InteractiveExample
component={<FlingGestureBasic/>}
src={FlingGestureBasicSrc}
disableMarginBottom={true}
/>
</div>

import BaseEventData from './\_shared/base-gesture-event-data.md';
import BaseEventConfig from './\_shared/base-gesture-config.md';
import BaseEventCallbacks from './\_shared/base-gesture-callbacks.md';

Gesture gets ACTIVE when movement is sufficiently long and it does not take too much time.
When gesture gets activated it will turn into END state when finger is released.
The gesture will fail to recognize if the finger is lifted before being activated.

<samp id="FlingGestureBasicSrc">Fling Gesture</samp>

Example

jsx
import { StyleSheet } from 'react-native';
import {
Gesture,
GestureDetector,
Directions,
} from 'react-native-gesture-handler';
import Animated, {
useSharedValue,
useAnimatedStyle,
withTiming,
} from 'react-native-reanimated';

export default function App() {
const position = useSharedValue(0);
// highlight-next-line
const flingGesture = Gesture.Fling()
.direction(Directions.RIGHT)
.onStart((e) => {
position.value = withTiming(position.value + 10, { duration: 100 });
});

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

return (
<GestureDetector gesture={flingGesture}>
<Animated.View style={[styles.box, animatedStyle]} />
</GestureDetector>
);
}

const styles = StyleSheet.create({
box: {
height: 120,
width: 120,
backgroundColor: '#b58df1',
borderRadius: 20,
marginBottom: 30,
},
});

text

Config

Properties specific to FlingGesture:

direction(value: Directions)

Expresses the allowed direction of movement. Expected values are exported as constants in the Directions object. It's possible to pass one or many directions in one parameter:

js
import { Directions } from 'react-native-gesture-handler';
fling.direction(Directions.RIGHT | Directions.LEFT);
text
or
js
fling.direction(Directions.DOWN);
text

numberOfPointers(value: number)

Determines the exact number of pointers required to handle the fling gesture.

mouseButton(value: MouseButton) (Web & Android only)

Allows users to choose which mouse button should handler respond to. The enum MouseButton consists of the following predefined fields:

- LEFT
-
RIGHT
-
MIDDLE
-
BUTTON_4
-
BUTTON_5
-
ALL

Arguments can be combined using | operator, e.g. mouseButton(MouseButton.LEFT | MouseButton.RIGHT). Default value is set to MouseButton.LEFT.

<BaseEventConfig />

Callbacks

<BaseEventCallbacks />

Event data

Event attributes specific to FlingGesture:

x

X coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the GestureDetector. Expressed in point units.

y

Y coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the GestureDetector. Expressed in point units.

absoluteX

X coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the window. The value is expressed in point units. It is recommended to use it instead of x in cases when the original view can be transformed as an effect of the gesture.

absoluteY

Y coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the window. The value is expressed in point units. It is recommended to use it instead of y in cases when the original view can be transformed as an effect of the gesture.

<BaseEventData />

---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gestures/Force Touch Gesture (packages/docs-gesture-handler/versioned_docs/version-2.x/gestures/force-touch-gesture.md)

---
id: force-touch-gesture
title: Force touch gesture (iOS only)
sidebar_label: Force touch gesture
sidebar_position: 10
---

:::warning
ForceTouch gesture is deprecated and will be removed in the future version of Gesture Handler.
:::

import BaseEventData from './\_shared/base-gesture-event-data.md';
import BaseEventConfig from './\_shared/base-gesture-config.md';
import BaseContinuousEventConfig from './\_shared/base-continuous-gesture-config.md';
import BaseEventCallbacks from './\_shared/base-gesture-callbacks.md';
import BaseContinuousEventCallbacks from './\_shared/base-continuous-gesture-callbacks.md';

A continuous gesture that recognizes force of a touch. It allows for tracking pressure of touch on some iOS devices.
The gesture activates when pressure of touch is greater than or equal to
minForce. It fails if pressure is greater than maxForce.
Gesture callback can be used for continuous tracking of the touch pressure. It provides information for one finger (the first one).

At the beginning of the gesture, the pressure factor is 0.0. As the pressure increases, the pressure factor increases proportionally. The maximum pressure is 1.0.

There's no implementation provided on Android and it simply renders children without any wrappers.
Since this behavior is only provided on some iOS devices, this gesture should not be used for defining any crucial behaviors. Use it only as an additional improvement and make all features accessible without this gesture as well.

Reference

jsx
import { GestureDetector, Gesture } from 'react-native-gesture-handler';

function App() {
// highlight-next-line
const forceTouch = Gesture.ForceTouch();

return (
<GestureDetector gesture={forceTouch}>
<View />
</GestureDetector>
);
}

text

Config

Properties specific to ForceTouchGesture:

minForce(value: number)

A minimal pressure that is required before gesture can activate. Should be a value from range [0.0, 1.0]. Default is 0.2.

maxForce(value: number)

A maximal pressure that could be applied for gesture. If the pressure is greater, gesture fails. Should be a value from range [0.0, 1.0].

feedbackOnActivation(value: boolean)

Value defining if haptic feedback has to be performed on activation.

<BaseEventConfig />
<BaseContinuousEventConfig />

Callbacks

<BaseEventCallbacks />
<BaseContinuousEventCallbacks />

Event data

Event attributes specific to ForceTouchGesture:

force

The pressure of a touch.

<BaseEventData />

---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gestures/Gesture Detector (packages/docs-gesture-handler/versioned_docs/version-2.x/gestures/gesture-detector.md)

---
id: gesture-detector
title: GestureDetector
sidebar_label: Gesture detector
sidebar_position: 1
---

import FunctionalComponents from './\_shared/gesture-detector-functional1.md';

GestureDetector is the main component of the RNGH2. It is responsible for creating and updating native gesture handlers based on the config of the provided gesture. The most significant difference between it and old gesture handlers is that the GestureDetector can recognize more than one gesture at a time thanks to gesture composition. Keep in mind that GestureDetector is not compatible with the Animated API, nor with Reanimated 1.

Reference

javascript
import { Gesture, GestureDetector } from 'react-native-gesture-handler';

function App() {
const tap = Gesture.Tap();
return (
// highlight-next-line
<GestureDetector gesture={tap}>
<Animated.View />
// highlight-next-line
</GestureDetector>
);
}

text

Properties

gesture

A gesture object containing the configuration and callbacks. Can be any of the base gestures (Tap, Pan, LongPress, Fling, Pinch, Rotation, ForceTouch) or any ComposedGesture (Race, Simultaneous, Exclusive).

:::info
GestureDetector will decide whether to use Reanimated to process provided gestures based on callbacks they have. If any of the callbacks is a worklet, tools provided by Reanimated will be utilized, bringing the ability to handle gestures synchronously.

Starting with Reanimated 2.3.0 Gesture Handler will provide a StateManager in the touch events that allows for managing the state of the gesture.
:::

userSelect (Web only)

This parameter allows to specify which userSelect property should be applied to underlying view. Possible values are "none" | "auto" | "text". Default value is set to "none".

touchAction (Web only)

This parameter allows to specify which touchAction property should be applied to underlying view. Supports all CSS touch-action values (e.g. "none", "pan-y"). Default value is set to "none".

enableContextMenu(value: boolean) (Web only)

Specifies whether context menu should be enabled after clicking on underlying view with right mouse button. Default value is set to false.

Remarks

- Gesture Detector will use first native view in its subtree to recognize gestures, however if this view is used only to group its children it may get automatically collapsed. Consider this example:
<FunctionalComponents />
If we were to remove the collapsable prop from the View, the gesture would stop working because it would be attached to a view that is not present in the view hierarchy. Gesture Detector adds this prop automatically to its direct child but it's impossible to do automatically for more complex view trees.

- Using the same instance of a gesture across multiple Gesture Detectors is not possible. Have a look at the code below:

jsx
export default function Example() {
const pan = Gesture.Pan();

return (
<View>
<GestureDetector gesture={pan}>
<View>
<GestureDetector gesture={pan}>
{' '}
{/ Don't do this! /}
<View />
</GestureDetector>
</View>
</GestureDetector>
</View>
);
}

text
This example will throw an error, because we try to use the same instance of Pan in two different Gesture Detectors.

---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gestures/Gesture (packages/docs-gesture-handler/versioned_docs/version-2.x/gestures/gesture.md)

---
id: gesture
title: Gesture
sidebar_label: Gesture
sidebar_position: 2
---

Gesture is the object that allows you to create and compose gestures.

Reference

jsx
import { GestureDetector, Gesture } from 'react-native-gesture-handler';

function App() {
// highlight-next-line
const tap = Gesture.Tap();

return (
<GestureDetector gesture={tap}>
<Animated.View />
</GestureDetector>
);
}

text

Gesture.Tap()

Creates a new instance of TapGesture with its default config and no callbacks.

Gesture.Pan()

Creates a new instance of PanGesture with its default config and no callbacks.

Gesture.LongPress()

Creates a new instance of LongPressGesture with its default config and no callbacks.

Gesture.Fling()

Creates a new instance of FlingGesture with its default config and no callbacks.

Gesture.Pinch()

Creates a new instance of PinchGesture with its default config and no callbacks.

Gesture.Rotation()

Creates a new instance of RotationGesture with its default config and no callbacks.

Gesture.Hover()

Creates a new instance of HoverGesture with its default config and no callbacks.

Gesture.ForceTouch()

Creates a new instance of ForceTouchGesture with its default config and no callbacks.

Gesture.Manual()

Creates a new instance of ManualGesture with its default config and no callbacks.

Gesture.Native()

Creates a new instance of NativeGesture with its default config and no callbacks.

Gesture.Race(gesture1, gesture2, gesture3, ...): ComposedGesture

Creates a gesture composed of those provided as arguments. Only one of those can become active and there are no restrictions to the activation of the gesture. The first one to activate will cancel all the others.

Gesture.Simultaneous(gesture1, gesture2, gesture3, ...): ComposedGesture

Creates a gesture composed of those provided as arguments. All of them can become active without cancelling the others.

Gesture.Exclusive(gesture1, gesture2, gesture3, ...): ComposedGesture

Creates a gesture composed of those provided as arguments. Only one of them can become active, but the first one has a higher priority than the second one, the second one has a higher priority than the third one, and so on. When all gestures are in the BEGAN state and the activation criteria for the second one is met, instead of activating it will wait until the first one fails (and only then it will activate) or until the first one activates (and then the second one will get cancelled). It is useful when you want to compose gestures with similar activation criteria (e.g. single and double tap at the same component, without Exclusive the single tap would activate every time user taps thus cancelling the double tap).

Remarks

- Consider wrapping your gesture configurations with useMemo, as it will reduce the amount of work Gesture Handler has to do under the hood when updating gestures. For example:

jsx
import React from 'react';

function App() {
const gesture = React.useMemo(
() =>
Gesture.Tap().onStart(() => {
console.log('Number of taps:', tapNumber + 1);
setTapNumber((value) => value + 1);
}),
[tapNumber, setTapNumber]
);
// ...
}

text
---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gestures/Hover Gesture (packages/docs-gesture-handler/versioned_docs/version-2.x/gestures/hover-gesture.md)

---
id: hover-gesture
title: Hover gesture
sidebar_label: Hover gesture
sidebar_position: 9
---

import { webContainer } from '@site/src/utils/getGestureStyles';

import HoverGestureBasic from '../examples/HoverGestureBasic';
import HoverGestureBasicSrc from '!!raw-loader!../examples/HoverGestureBasic';

A continuous gesture that can recognize hovering above the view it's attached to.

<div className={webContainer}>
<InteractiveExample
component={<HoverGestureBasic/>}
src={HoverGestureBasicSrc}
disableMarginBottom={true}
/>
</div>

import BaseEventData from './\_shared/base-gesture-event-data.md';
import BaseEventConfig from './\_shared/base-gesture-config.md';
import BaseEventCallbacks from './\_shared/base-gesture-callbacks.md';
import BaseContinuousEventCallbacks from './\_shared/base-continuous-gesture-callbacks.md';

The hover effect may be activated by moving a mouse or a stylus over the view.

On iOS additional visual effects may be configured.

<samp id="HoverGestureBasic">Hover Gesture</samp>

Reference

jsx
import { GestureDetector, Gesture } from 'react-native-gesture-handler';

function App() {
// highlight-next-line
const hover = Gesture.Hover();

return (
<GestureDetector gesture={hover}>
<View />
</GestureDetector>
);
}

text

Remarks

- Don't rely on Hover gesture to continue after the mouse button is clicked or the stylus touches the screen. If you want to handle both cases, compose it with Pan gesture.

Config

Properties specific to HoverGesture:

effect(effect: HoverEffect) (iOS only)

js
import { HoverEffect } from 'react-native-gesture-handler';
text
Visual effect applied to the view while the view is hovered. The possible values are:

- HoverEffect.None
-
HoverEffect.Lift
-
HoverEffect.Highlight

Defaults to HoverEffect.None

<BaseEventConfig />

Callbacks

<BaseEventCallbacks />
<BaseContinuousEventCallbacks />

Event data

Event attributes specific to HoverGesture:

x

X coordinate of the current position of the pointer relative to the view attached to the GestureDetector. Expressed in point units.

y

Y coordinate of the current position of the pointer relative to the view attached to the GestureDetector. Expressed in point units.

absoluteX

X coordinate of the current position of the pointer relative to the window. The value is expressed in point units. It is recommended to use it instead of x in cases when the original view can be transformed as an effect of the gesture.

absoluteY

Y coordinate of the current position of the pointer relative to the window. The value is expressed in point units. It is recommended to use it instead of y in cases when the original view can be transformed as an effect of the gesture.

stylusData

Object that contains additional information about stylus. It consists of the following fields:

- tiltX - angle in degrees between the Y-Z plane of the stylus and the screen.
-
tiltY - angle in degrees between the X-Z plane of the stylus and the screen.
-
altitudeAngle - angle between stylus axis and the X-Y plane of a device screen.
-
azimuthAngle - angle between the Y-Z plane and the plane containing both the stylus axis and the Y axis.
-
pressure - indicates the normalized pressure of the stylus.

<BaseEventData />

---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gestures/Long Press Gesture (packages/docs-gesture-handler/versioned_docs/version-2.x/gestures/long-press-gesture.md)

---
id: long-press-gesture
title: Long press gesture
sidebar_label: Long press gesture
sidebar_position: 5
---

import { webContainer } from '@site/src/utils/getGestureStyles';

import LongPressGestureBasic from '../examples/LongPressGestureBasic';
import LongPressGestureBasicSrc from '!!raw-loader!../examples/LongPressGestureBasic';

A discrete gesture that activates when the corresponding view is pressed for a sufficiently long time.

<div className={webContainer}>
<InteractiveExample
component={<LongPressGestureBasic/>}
src={LongPressGestureBasicSrc}
disableMarginBottom={true}
/>
</div>

import BaseEventData from './\_shared/base-gesture-event-data.md';
import BaseEventConfig from './\_shared/base-gesture-config.md';
import BaseEventCallbacks from './\_shared/base-gesture-callbacks.md';

This gesture's state will turn into END immediately after the finger is released.
The gesture will fail to recognize a touch event if the finger is lifted before the minimum required time or if the finger is moved further than the allowable distance.

<samp id="LongPressGestureBasic">Long Press Gesture</samp>

Example

jsx
import { View, StyleSheet } from 'react-native';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';

export default function App() {
// highlight-next-line
const longPressGesture = Gesture.LongPress().onEnd((e, success) => {
if (success) {
console.log(
Long pressed for ${e.duration} ms!);
}
});

return (
<GestureDetector gesture={longPressGesture}>
<View style={styles.box} />
</GestureDetector>
);
}

const styles = StyleSheet.create({
box: {
height: 120,
width: 120,
backgroundColor: '#b58df1',
borderRadius: 20,
marginBottom: 30,
},
});

text

Config

Properties specific to LongPressGesture:

minDuration(value: number)

Minimum time, expressed in milliseconds, that a finger must remain pressed on the corresponding view. The default value is 500.

maxDistance(value: number)

Maximum distance, expressed in points, that defines how far the finger is allowed to travel during a long press gesture. If the finger travels further than the defined distance and the gesture hasn't yet activated, it will fail to recognize the gesture. The default value is 10.

mouseButton(value: MouseButton) (Web & Android only)

Allows users to choose which mouse button should handler respond to. The enum MouseButton consists of the following predefined fields:

- LEFT
-
RIGHT
-
MIDDLE
-
BUTTON_4
-
BUTTON_5
-
ALL

Arguments can be combined using | operator, e.g. mouseButton(MouseButton.LEFT | MouseButton.RIGHT). Default value is set to MouseButton.LEFT.

<BaseEventConfig />

Callbacks

<BaseEventCallbacks />

Event data

Event attributes specific to LongPressGesture:

x

X coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the GestureDetector.

y

Y coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the GestureDetector.

absoluteX

X coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the window. It is recommended to use absoluteX instead of x in cases when the view attached to the GestureDetector can be transformed as an effect of the gesture.

absoluteY

Y coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the window. It is recommended to use absoluteY instead of y in cases when the view attached to the GestureDetector can be transformed as an effect of the gesture.

duration

Duration of the long press (time since the start of the gesture), expressed in milliseconds.

<BaseEventData />

---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gestures/Manual Gesture (packages/docs-gesture-handler/versioned_docs/version-2.x/gestures/manual-gesture.md)

---
id: manual-gesture
title: Manual gesture
sidebar_label: Manual gesture
sidebar_position: 12
---

import BaseEventData from './\_shared/base-gesture-event-data.md';
import BaseEventConfig from './\_shared/base-gesture-config.md';
import BaseEventCallbacks from './\_shared/base-gesture-callbacks.md';
import BaseContinuousEventCallbacks from './\_shared/base-continuous-gesture-callbacks.md';

A plain gesture that has no specific activation criteria nor event data set. Its state has to be controlled manually using a state manager. It will not fail when all the pointers are lifted from the screen.

Reference

jsx
import { GestureDetector, Gesture } from 'react-native-gesture-handler';

function App() {
// highlight-next-line
const manual = Gesture.Manual();

return (
<GestureDetector gesture={manual}>
<Animated.View />
</GestureDetector>
);
}

text

Config

<BaseEventConfig />

Callbacks

<BaseEventCallbacks />
<BaseContinuousEventCallbacks />

Event data

<BaseEventData />

---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gestures/Native Gesture (packages/docs-gesture-handler/versioned_docs/version-2.x/gestures/native-gesture.md)

---
id: native-gesture
title: Native gesture
sidebar_label: Native gesture
sidebar_position: 11
---

import BaseEventData from './\_shared/base-gesture-event-data.md';
import BaseEventConfig from './\_shared/base-gesture-config.md';
import BaseEventCallbacks from './\_shared/base-gesture-callbacks.md';
import BaseContinuousEventCallbacks from './\_shared/base-continuous-gesture-callbacks.md';

A gesture that allows other touch handling components to work within RNGH's gesture system. This streamlines interactions between gestures and the native component, allowing it to form relations with other gestures.

When used, the native component should be the direct child of a GestureDetector.

Example

This example renders a ScrollView with multiple colored rectangles, where each rectangle has a black section. Starting a touch on a black section will disable the ScrollView for the duration of the Pan gesture.

jsx
import { View, ScrollView } from 'react-native';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';

const COLORS = ['red', 'green', 'blue', 'purple', 'orange', 'cyan'];

export default function App() {
// highlight-next-line
const native = Gesture.Native();

return (
<GestureDetector gesture={native}>
<ScrollView style={{ flex: 1 }}>
<ScrollableContent scrollGesture={native} />
</ScrollView>
</GestureDetector>
);
}

function ScrollableContent({ scrollGesture }) {
return (
<View>
{COLORS.map((color) => (
<Rectangle key={color} color={color} scrollGesture={scrollGesture} />
))}
</View>
);
}

function Rectangle({ color, scrollGesture }) {
const pan = Gesture.Pan().blocksExternalGesture(scrollGesture);

return (
<View
key={color}
style={{ width: '100%', height: 250, backgroundColor: color }}>
<GestureDetector gesture={pan}>
<View style={{ width: '100%', height: 50, backgroundColor: 'black' }} />
</GestureDetector>
</View>
);
}

text

Remarks

- Native gesture can be used as part of gesture composition and cross-component interactions just like any other gesture. You can use this to block a native component for the duration of the gesture or to make it work alongside a gesture.

:::danger
Do not use
Native gesture with components exported by React Native Gesture Handler. Those come with a native gesture handler preapplied. Attaching a native gesture twice will likely result in the components not working as intended.
:::

Config

Properties specific to NativeGesture:

shouldActivateOnStart(value: boolean) (Android only)

When true, underlying handler will activate unconditionally when it receives any touches in BEGAN or UNDETERMINED state.

disallowInterruption(value: boolean)

When true, cancels all other gesture handlers when this NativeViewGestureHandler changes its state to ACTIVE.

<BaseEventConfig />

Callbacks

<BaseEventCallbacks />

Event data

Event attributes specific to NativeGesture:

pointerInside

True if gesture was performed inside of containing view, false otherwise.

<BaseEventData />

---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gestures/Pan Gesture (packages/docs-gesture-handler/versioned_docs/version-2.x/gestures/pan-gesture.md)

---
id: pan-gesture
title: Pan gesture
sidebar_label: Pan gesture
sidebar_position: 3
---

import { webContainer } from '@site/src/utils/getGestureStyles';

import PanGestureBasic from '../examples/PanGestureBasic';
import PanGestureBasicSrc from '!!raw-loader!../examples/PanGestureBasic';

A continuous gesture that can recognize a panning (dragging) gesture and track its movement.

<div className={webContainer}>
<InteractiveExample
component={<PanGestureBasic/>}
src={PanGestureBasicSrc}
disableMarginBottom={true}
/>
</div>

import BaseEventData from './\_shared/base-gesture-event-data.md';
import BaseEventConfig from './\_shared/base-gesture-config.md';
import BaseContinuousEventConfig from './\_shared/base-continuous-gesture-config.md';
import BaseEventCallbacks from './\_shared/base-gesture-callbacks.md';
import BaseContinuousEventCallbacks from './\_shared/base-continuous-gesture-callbacks.md';

The gesture activates when a finger is placed on the screen and moved some initial distance.

Configurations such as a minimum initial distance, specific vertical or horizontal pan detection and number of fingers required for activation (allowing for multifinger swipes) may be specified.

Gesture callback can be used for continuous tracking of the pan gesture. It provides information about the gesture such as its XY translation from the starting point as well as its instantaneous velocity.

<samp id="PanGestureBasicSrc">Pan Gesture</samp>

Example

jsx
import { StyleSheet } from 'react-native';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import Animated, {
useSharedValue,
withTiming,
useAnimatedStyle,
} from 'react-native-reanimated';

const END_POSITION = 200;

export default function App() {
const onLeft = useSharedValue(true);
const position = useSharedValue(0);

// highlight-next-line
const panGesture = Gesture.Pan()
.onUpdate((e) => {
if (onLeft.value) {
position.value = e.translationX;
} else {
position.value = END_POSITION + e.translationX;
}
})
.onEnd((e) => {
if (position.value > END_POSITION / 2) {
position.value = withTiming(END_POSITION, { duration: 100 });
onLeft.value = false;
} else {
position.value = withTiming(0, { duration: 100 });
onLeft.value = true;
}
});

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

return (
// highlight-next-line
<GestureDetector gesture={panGesture}>
<Animated.View style={[styles.box, animatedStyle]} />
</GestureDetector>
);
}

const styles = StyleSheet.create({
box: {
height: 120,
width: 120,
backgroundColor: '#b58df1',
borderRadius: 20,
marginBottom: 30,
},
});

text

Multi touch pan handling

If your app relies on multi touch pan handling this section provides some information on how the default behavior differs between platforms and how (if necessary) it can be unified.

The difference in multi touch pan handling lies in the way translation properties during the event are being calculated.
On iOS the default behavior when more than one finger is placed on the screen is to treat this situation as if only one pointer was placed in the center of mass (average position of all the pointers).
This applies also to many platform native components that handle touch even if not primarily interested in multi touch interactions like for example UIScrollView component.

On Android, the default behavior for native components like scroll view, pager views or drawers is different and hence gesture defaults to that when it comes to pan handling.
The difference is that instead of treating the center of mass of all the fingers placed as a leading pointer it takes the latest placed finger as such.
This behavior can be changed on Android using
averageTouches flag.

Note that on both Android and iOS when the additional finger is placed on the screen that translation prop is not affected even though the position of the pointer being tracked might have changed.
Therefore it is safe to rely on translation most of the time as it only reflects the movement that happens regardless of how many fingers are placed on the screen and if that number changes over time.
If you wish to track the "center of mass" virtual pointer and account for its changes when the number of finger changes you can use relative or absolute position provided in the event (
x and y or absoluteX and absoluteY).

Config

Properties specific to PanGesture:

minDistance(value: number)

Minimum distance the finger (or multiple fingers) need to travel before the gesture activates. Expressed in points.

minVelocity(value: number)

Minimum speed the pointer has to reach in order for the gesture to activate. Expressed in points per second.

minVelocityX(value: number)

Minimum speed along X axis the pointer has to reach in order for the gesture to activate. Expressed in points per second.

minVelocityY(value: number)

Minimum speed along Y axis the pointer has to reach in order for the gesture to activate. Expressed in points per second.

minPointers(value: number)

A number of fingers that is required to be placed before the gesture can activate. Should be an integer greater than or equal to 0.

maxPointers(value: number)

When the given number of fingers is placed on the screen and gesture hasn't yet activated it will fail recognizing the gesture. Should be an integer greater than or equal to 0.

activateAfterLongPress(duration: number)

Duration in milliseconds of the LongPress gesture before Pan is allowed to activate. If the finger is moved during that period, the gesture will fail. Should be an integer greater than or equal to 0. Default value is 0, meaning no LongPress is required to activate the Pan.

activeOffsetX(value: number | number[])

Range along X axis (in points) where fingers travel without activation of gesture. Moving outside of this range implies activation of gesture. Range can be given as an array or a single number.
If range is set as an array, the first value must be lower or equal to 0 and the second one higher or equal to 0.
If only one number
p is given a range of (-inf, p) will be used if p is higher or equal to 0 and (-p, inf) otherwise.

activeOffsetY(value: number | number[])

Range along Y axis (in points) where fingers travel without activation of gesture. Moving outside of this range implies activation of gesture. Range can be given as an array or a single number.
If range is set as an array, the first value must be lower or equal to 0 and the second one higher or equal to 0.
If only one number
p is given a range of (-inf, p) will be used if p is higher or equal to 0 and (-p, inf) otherwise.

failOffsetY(value: number | number[])

When the finger moves outside this range (in points) along Y axis and gesture hasn't yet activated it will fail recognizing the gesture. Range can be given as an array or a single number.
If range is set as an array, the first value must be lower or equal to 0 and the second one higher or equal to 0.
If only one number
p is given a range of (-inf, p) will be used if p is higher or equal to 0 and (-p, inf) otherwise.

failOffsetX(value: number | number[])

When the finger moves outside this range (in points) along X axis and gesture hasn't yet activated it will fail recognizing the gesture. Range can be given as an array or a single number.
If range is set as an array, the first value must be lower or equal to 0 and the second one higher or equal to 0.
If only one number
p is given a range of (-inf, p) will be used if p is higher or equal to 0 and (-p, inf) otherwise.

averageTouches(value: boolean) (Android only)

Android, by default, will calculate translation values based on the position of the leading pointer (the first one that was placed on the screen). This modifier allows that behavior to be changed to the one that is default on iOS - the averaged position of all active pointers will be used to calculate the translation values.

enableTrackpadTwoFingerGesture(value: boolean) (iOS only)

Enables two-finger gestures on supported devices, for example iPads with trackpads. If not enabled the gesture will require click + drag, with enableTrackpadTwoFingerGesture swiping with two fingers will also trigger the gesture.

mouseButton(value: MouseButton) (Web & Android only)

Allows users to choose which mouse button should handler respond to. The enum MouseButton consists of the following predefined fields:

- LEFT
-
RIGHT
-
MIDDLE
-
BUTTON_4
-
BUTTON_5
-
ALL

Arguments can be combined using | operator, e.g. mouseButton(MouseButton.LEFT | MouseButton.RIGHT). Default value is set to MouseButton.LEFT.

<BaseEventConfig />
<BaseContinuousEventConfig />

Callbacks

<BaseEventCallbacks />
<BaseContinuousEventCallbacks />

Event data

Event attributes specific to PanGesture:

translationX

Translation of the pan gesture along X axis accumulated over the time of the gesture. The value is expressed in the point units.

translationY

Translation of the pan gesture along Y axis accumulated over the time of the gesture. The value is expressed in the point units.

velocityX

Velocity of the pan gesture along the X axis in the current moment. The value is expressed in point units per second.

velocityY

Velocity of the pan gesture along the Y axis in the current moment. The value is expressed in point units per second.

x

X coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the GestureDetector. Expressed in point units.

y

Y coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the GestureDetector. Expressed in point units.

absoluteX

X coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the window. The value is expressed in point units. It is recommended to use it instead of x in cases when the original view can be transformed as an effect of the gesture.

absoluteY

Y coordinate of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the window. The value is expressed in point units. It is recommended to use it instead of y in cases when the original view can be transformed as an effect of the gesture.

stylusData

Object that contains additional information about stylus. It consists of the following fields:

- tiltX - angle in degrees between the Y-Z plane of the stylus and the screen.
-
tiltY - angle in degrees between the X-Z plane of the stylus and the screen.
-
altitudeAngle - angle between stylus axis and the X-Y plane of a device screen.
-
azimuthAngle - angle between the Y-Z plane and the plane containing both the stylus axis and the Y axis.
-
pressure - indicates the normalized pressure of the stylus.

<BaseEventData />

---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gestures/Pinch Gesture (packages/docs-gesture-handler/versioned_docs/version-2.x/gestures/pinch-gesture.md)

---
id: pinch-gesture
title: Pinch gesture
sidebar_label: Pinch gesture
sidebar_position: 7
---

import { webContainer } from '@site/src/utils/getGestureStyles';

import PinchGestureBasic from '../examples/PinchGestureBasic';
import PinchGestureBasicSrc from '!!raw-loader!../examples/PinchGestureBasicSrc';

A continuous gesture that recognizes a pinch gesture. It allows for tracking the distance between two fingers and using that information to scale or zoom your content.

<div className={webContainer}>
<InteractiveExample
component={<PinchGestureBasic/>}
src={PinchGestureBasicSrc}
disableMarginBottom={true}
/>
</div>

import BaseEventData from './\_shared/base-gesture-event-data.md';
import BaseEventConfig from './\_shared/base-gesture-config.md';
import BaseContinuousEventConfig from './\_shared/base-continuous-gesture-config.md';
import BaseEventCallbacks from './\_shared/base-gesture-callbacks.md';
import BaseContinuousEventCallbacks from './\_shared/base-continuous-gesture-callbacks.md';

The gesture activates when fingers are placed on the screen and change their position.
Gesture callback can be used for continuous tracking of the pinch gesture. It provides information about velocity, anchor (focal) point of gesture and scale.

The distance between the fingers is reported as a scale factor. At the beginning of the gesture, the scale factor is 1.0. As the distance between the two fingers increases, the scale factor increases proportionally.
Similarly, the scale factor decreases as the distance between the fingers decreases.
Pinch gestures are used most commonly to change the size of objects or content onscreen.
For example, map views use pinch gestures to change the zoom level of the map.

<samp id="PinchGestureBasicSrc">Pinch Gesture</samp>

Example

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

export default function App() {
const scale = useSharedValue(1);
const savedScale = useSharedValue(1);

// highlight-next-line
const pinchGesture = Gesture.Pinch()
.onUpdate((e) => {
scale.value = savedScale.value * e.scale;
})
.onEnd(() => {
savedScale.value = scale.value;
});

const animatedStyle = useAnimatedStyle(() => ({
transform: [{ scale: scale.value }],
}));

return (
<GestureDetector gesture={pinchGesture}>
<Animated.View style={[styles.box, animatedStyle]} />
</GestureDetector>
);
}

const styles = StyleSheet.create({
box: {
height: 120,
width: 120,
backgroundColor: '#b58df1',
borderRadius: 20,
marginBottom: 30,
},
});

text

Remarks

- When implementing pinch based on focal point, make sure to use it after gesture has activated, i.e. in onStart, onUpdate or onChange callbacks. Using it in onBegan may lead to unexpected behavior.

Config

<BaseEventConfig />
<BaseContinuousEventConfig />

Callbacks

<BaseEventCallbacks />
<BaseContinuousEventCallbacks />

Event data

Event attributes specific to PinchGesture:

scale

The scale factor relative to the points of the two touches in screen coordinates.

velocity

Velocity of the pinch gesture at the current moment. The value is expressed in scale factor per second.

focalX

Position expressed in points along the X axis of the center anchor point of the gesture.

focalY

Position expressed in points along the Y axis of the center anchor point of the gesture.

<BaseEventData />

---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gestures/Rotation Gesture (packages/docs-gesture-handler/versioned_docs/version-2.x/gestures/rotation-gesture.md)

---
id: rotation-gesture
title: Rotation gesture
sidebar_label: Rotation gesture
sidebar_position: 6
---

import { webContainer } from '@site/src/utils/getGestureStyles';

import RotationGestureBasic from '../examples/RotationGestureBasic';
import RotationGestureBasicSrc from '!!raw-loader!../examples/RotationGestureBasicSrc';

A continuous gesture that can recognize a rotation gesture and track its movement.

<div className={webContainer}>
<InteractiveExample
component={<RotationGestureBasic/>}
src={RotationGestureBasicSrc}
disableMarginBottom={true}
/>
</div>

import BaseEventData from './\_shared/base-gesture-event-data.md';
import BaseEventConfig from './\_shared/base-gesture-config.md';
import BaseContinuousEventConfig from './\_shared/base-continuous-gesture-config.md';
import BaseEventCallbacks from './\_shared/base-gesture-callbacks.md';
import BaseContinuousEventCallbacks from './\_shared/base-continuous-gesture-callbacks.md';

The gesture activates when fingers are placed on the screen and change position in a proper way.

Gesture callback can be used for continuous tracking of the rotation gesture. It provides information about the gesture such as the amount rotated, the focal point of the rotation (anchor), and its instantaneous velocity.

<samp id="RotationGestureBasicSrc">Rotation Gesture</samp>

Example

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

export default function App() {
const rotation = useSharedValue(1);
const savedRotation = useSharedValue(1);

// highlight-next-line
const rotationGesture = Gesture.Rotation()
.onUpdate((e) => {
rotation.value = savedRotation.value + e.rotation;
})
.onEnd(() => {
savedRotation.value = rotation.value;
});

const animatedStyle = useAnimatedStyle(() => ({
transform: [{ rotateZ:
${(rotation.value / Math.PI) * 180}deg }],
}));

return (
<GestureDetector gesture={rotationGesture}>
<Animated.View style={[styles.box, animatedStyle]} />
</GestureDetector>
);
}

const styles = StyleSheet.create({
box: {
height: 120,
width: 120,
backgroundColor: '#b58df1',
borderRadius: 20,
marginBottom: 30,
},
});

text

Remarks

- When implementing rotation based on anchor point, make sure to use it after gesture has activated, i.e. in onStart, onUpdate or onChange callbacks. Using it in onBegan may lead to unexpected behavior.

Config

<BaseEventConfig />
<BaseContinuousEventConfig />

Callbacks

<BaseEventCallbacks />
<BaseContinuousEventCallbacks />

Event data

Event attributes specific to RotationGesture:

rotation

Amount rotated, expressed in radians, from the gesture's focal point (anchor).

velocity

Instantaneous velocity, expressed in point units per second, of the gesture.

anchorX

X coordinate, expressed in points, of the gesture's central focal point (anchor).

anchorY

Y coordinate, expressed in points, of the gesture's central focal point (anchor).

<BaseEventData />

---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gestures/State Manager (packages/docs-gesture-handler/versioned_docs/version-2.x/gestures/state-manager.md)

---
id: state-manager
title: Gesture state manager
sidebar_label: Gesture state manager
sidebar_position: 15
---

GestureStateManager allows you to manually control the state of the gestures. Please note that react-native-reanimated is required to use it, since it allows for synchronously executing methods in worklets.

Methods

begin()

Transition the gesture to the BEGAN state. This method will have no effect if the gesture has already activated or finished.

activate()

Transition the gesture to the ACTIVE state. This method will have no effect if the handler is already active, or has finished.
If the gesture is
exclusive with another one, the activation will be delayed until the gesture with higher priority fails.

end()

Transition the gesture to the END state. This method will have no effect if the handler has already finished.

fail()

Transition the gesture to the FAILED state. This method will have no effect if the handler has already finished.

---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gestures/Tap Gesture (packages/docs-gesture-handler/versioned_docs/version-2.x/gestures/tap-gesture.md)

---
id: tap-gesture
title: Tap gesture
sidebar_label: Tap gesture
sidebar_position: 4
---

import { webContainer } from '@site/src/utils/getGestureStyles';

import TapGestureBasic from '../examples/TapGestureBasic';
import TapGestureBasicSrc from '!!raw-loader!../examples/TapGestureBasic';

A discrete gesture that recognizes one or many taps.

<div className={webContainer}>
<InteractiveExample
component={<TapGestureBasic/>}
src={TapGestureBasicSrc}
disableMarginBottom={true}
/>
</div>

import BaseEventData from './\_shared/base-gesture-event-data.md';
import BaseEventConfig from './\_shared/base-gesture-config.md';
import BaseEventCallbacks from './\_shared/base-gesture-callbacks.md';

Tap gestures detect one or more fingers briefly touching the screen.
The fingers involved in these gestures must not move significantly from their initial touch positions.
The required number of taps and allowed distance from initial position may be configured.
For example, you might configure tap gesture recognizers to detect single taps, double taps, or triple taps.

In order for a gesture to activate, specified gesture requirements such as minPointers, numberOfTaps, maxDist, maxDuration, and maxDelayMs (explained below) must be met. Immediately after the gesture activates, it will end.

<samp id="TapGestureBasic">Tap Gesture</samp>

Example

jsx
import { View, StyleSheet } from 'react-native';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';

export default function App() {
// highlight-next-line
const singleTap = Gesture.Tap()
.maxDuration(250)
.onStart(() => {
console.log('Single tap!');
});

// highlight-next-line
const doubleTap = Gesture.Tap()
.maxDuration(250)
.numberOfTaps(2)
.onStart(() => {
console.log('Double tap!');
});

return (
<GestureDetector gesture={Gesture.Exclusive(doubleTap, singleTap)}>
<View style={styles.box} />
</GestureDetector>
);
}

const styles = StyleSheet.create({
box: {
height: 120,
width: 120,
backgroundColor: '#b58df1',
borderRadius: 20,
marginBottom: 30,
},
});

text

Config

Properties specific to TapGesture:

minPointers(value: number)

Minimum number of pointers (fingers) required to be placed before the gesture activates. Should be a positive integer. The default value is 1.

maxDuration(value: number)

Maximum time, expressed in milliseconds, that defines how fast a finger must be released after a touch. The default value is 500.

maxDelay(value: number)

Maximum time, expressed in milliseconds, that can pass before the next tap — if many taps are required. The default value is 500.

numberOfTaps(value: number)

Number of tap gestures required to activate the gesture. The default value is 1.

maxDeltaX(value: number)

Maximum distance, expressed in points, that defines how far the finger is allowed to travel along the X axis during a tap gesture. If the finger travels further than the defined distance along the X axis and the gesture hasn't yet activated, it will fail to recognize the gesture.

maxDeltaY(value: number)

Maximum distance, expressed in points, that defines how far the finger is allowed to travel along the Y axis during a tap gesture. If the finger travels further than the defined distance along the Y axis and the gesture hasn't yet activated, it will fail to recognize the gesture.

maxDistance(value: number)

Maximum distance, expressed in points, that defines how far the finger is allowed to travel during a tap gesture. If the finger travels further than the defined distance and the gesture hasn't yet activated, it will fail to recognize the gesture.

mouseButton(value: MouseButton) (Web & Android only)

Allows users to choose which mouse button should handler respond to. The enum MouseButton consists of the following predefined fields:

- LEFT
-
RIGHT
-
MIDDLE
-
BUTTON_4
-
BUTTON_5
-
ALL

Arguments can be combined using | operator, e.g. mouseButton(MouseButton.LEFT | MouseButton.RIGHT). Default value is set to MouseButton.LEFT.

<BaseEventConfig />

Callbacks

<BaseEventCallbacks />

Event data

Event attributes specific to TapGesture:

x

X coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the GestureDetector.

y

Y coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the view attached to the GestureDetector.

absoluteX

X coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the window. It is recommended to use absoluteX instead of x in cases when the view attached to the GestureDetector can be transformed as an effect of the gesture.

absoluteY

Y coordinate, expressed in points, of the current position of the pointer (finger or a leading pointer when there are multiple fingers placed) relative to the window. It is recommended to use absoluteY instead of y in cases when the view attached to the GestureDetector can be transformed as an effect of the gesture.

<BaseEventData />

---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gestures/Touch Events (packages/docs-gesture-handler/versioned_docs/version-2.x/gestures/touch-events.md)

---
id: touch-events
title: Touch events
sidebar_label: Touch events
sidebar_position: 14
---

Touch event attributes:

eventType

Type of the current event - whether the finger was placed on the screen, moved, lifted or cancelled.

changedTouches

An array of objects where every object represents a single touch. Contains information only about the touches that were affected by the event i.e. those that were placed down, moved, lifted or cancelled.

allTouches

An array of objects where every object represents a single touch. Contains information about all active touches.

numberOfTouches

Number representing the count of currently active touches.

:::caution
Don't rely on the order of items in the
touches as it may change during the gesture, instead use the id attribute to track individual touches across events.
:::

PointerData attributes:

id

A number representing the id of the touch. It may be used to track the touch between events as the id will not change while it is being tracked.

x

X coordinate of the current position of the touch relative to the view attached to the GestureDetector. Expressed in point units.

y

Y coordinate of the current position of the touch relative to the view attached to the GestureDetector. Expressed in point units.

absoluteX

X coordinate of the current position of the touch relative to the window. The value is expressed in point units. It is recommended to use it instead of x in cases when the original view can be transformed as an effect of the gesture.

absoluteY

Y coordinate of the current position of the touch relative to the window. The value is expressed in point units. It is recommended to use it instead of y in cases when the original view can be transformed as an effect of the gesture.

---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gestures/ Shared/Base Continuous Gesture Callbacks (packages/docs-gesture-handler/versioned_docs/version-2.x/gestures/_shared/base-continuous-gesture-callbacks.md)

Callbacks common to all continuous gestures:

onUpdate(callback)

Set the callback that is being called every time the gesture receives an update while it's active.

onChange(callback)

Set the callback that is being called every time the gesture receives an update while it's active. This callback will receive information about change in value in relation to the last received event.

---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gestures/ Shared/Base Continuous Gesture Config (packages/docs-gesture-handler/versioned_docs/version-2.x/gestures/_shared/base-continuous-gesture-config.md)

Properties common to all continuous gestures:

manualActivation(value: boolean)

When true, the handler will not activate by itself even if its activation criteria are met. Instead, you can manipulate its state using state manager.

---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gestures/ Shared/Base Gesture Callbacks (packages/docs-gesture-handler/versioned_docs/version-2.x/gestures/_shared/base-gesture-callbacks.md)

Callbacks common to all gestures:

onBegin(callback)

Set the callback that is being called when the given gesture handler starts receiving touches. At the moment of this callback the handler is not yet in an active state and we don't know yet if it will recognize the gesture at all.

onStart(callback)

Set the callback that is being called when the gesture is recognized by the handler and it transitions to the active state.

onEnd(callback)

Set the callback that is being called when the gesture that was recognized by the handler finishes. It will be called only if the handler was previously in the active state.

onFinalize(callback)

Set the callback that is being called when the handler finalizes handling the gesture - the gesture was recognized and has finished or it failed to recognize.

onTouchesDown(callback)

Set the onTouchesDown callback which is called every time a finger is placed on the screen.

onTouchesMove(callback)

Set the onTouchesMove callback which is called every time a finger is moved on the screen.

onTouchesUp(callback)

Set the onTouchesUp callback which is called every time a finger is lifted from the screen.

onTouchesCancelled(callback)

Set the onTouchesCancelled callback which is called every time a finger stops being tracked, for example when the gesture finishes.

---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gestures/ Shared/Base Gesture Config (packages/docs-gesture-handler/versioned_docs/version-2.x/gestures/_shared/base-gesture-config.md)

Properties common to all gestures:

enabled(value: boolean)

Indicates whether the given handler should be analyzing the stream of touch events or not.
When set to
false, we can be sure that the handler's state will never become ACTIVE.
If the value gets updated while the handler has already started recognizing a gesture, then the handler's state will immediately change to
FAILED or CANCELLED (depending on its current state).
Default value is
true.

shouldCancelWhenOutside(value: boolean)

When true, the handler will cancel or fail recognition (depending on its current state) whenever the finger leaves the area of the connected view.
Default value of this property is different depending on the handler type.
Most handlers'
shouldCancelWhenOutside property defaults to false except for the LongPressGesture, TapGesture and NativeGesture (on Android and web) which default to true.

hitSlop(settings)

This parameter enables control over what part of the connected view area can be used to begin recognizing the gesture.
When a negative number is provided, the bounds of the view will reduce the area by the given number of points in each of the sides evenly.

Instead you can pass an object to specify how each boundary side should be reduced by providing different number of points for left, right, top or bottom sides.
You can alternatively provide
horizontal or vertical instead of specifying directly left, right or top and bottom.
Finally, the object can also take
width and height attributes.
When
width is set it is only allowed to specify one of the sides right or left.
Similarly when
height is provided only top or bottom can be set.
Specifying
width or height is useful if we only want the gesture to activate on the edge of the view. In which case for example we can set left: 0 and width: 20 which would make it possible for the gesture to be recognized when started no more than 20 points from the left edge.

IMPORTANT: Note that this parameter is primarily designed to reduce the area where gesture can activate. Hence it is only supported for all the values (except width and height) to be non positive (0 or lower). Although on Android it is supported for the values to also be positive and therefore allow to expand beyond view bounds but not further than the parent view bounds. To achieve this effect on both platforms you can use React Native's View hitSlop property.

withRef(ref)

Sets a ref to the gesture object, allowing for interoperability with the old
API.

withTestId(testID)

Sets a testID property for gesture object, allowing for querying for it in tests.

cancelsTouchesInView(value) (iOS only)

Accepts a boolean value.
When
true, the gesture will cancel touches for native UI components (UIButton, UISwitch, etc) it's attached to when it becomes ACTIVE.
Default value is
true.

runOnJS(value: boolean)

When react-native-reanimated is installed, the callbacks passed to the gestures are automatically workletized and run on the UI thread when called. This option allows for changing this behavior: when true, all the callbacks will be run on the JS thread instead of the UI thread, regardless of whether they are worklets or not.
Defaults to
false.

simultaneousWithExternalGesture(otherGesture1, otherGesture2, ...)

Adds a gesture that should be recognized simultaneously with this one.

IMPORTANT: Note that this method only marks the relation between gestures, without composing them. GestureDetector will not recognize the otherGestures and it needs to be added to another detector in order to be recognized.

requireExternalGestureToFail(otherGesture1, otherGesture2, ...)

Adds a relation requiring another gesture to fail, before this one can activate.

blocksExternalGesture(otherGesture1, otherGesture2, ...)

Adds a relation that makes other gestures wait with activation until this gesture fails (or doesn't start at all).

IMPORTANT: Note that this method only marks the relation between gestures, without composing them. GestureDetector will not recognize the otherGestures and it needs to be added to another detector in order to be recognized.

activeCursor(value) (Web only)

This parameter allows specifying which cursor should be used when the gesture activates. Supports all CSS cursor values (e.g. "grab", "zoom-in"). Default value is set to "auto".

---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gestures/ Shared/Base Gesture Event Data (packages/docs-gesture-handler/versioned_docs/version-2.x/gestures/_shared/base-gesture-event-data.md)

Event attributes common to all gestures:

state

Current state of the handler. Expressed as one of the constants exported under State object by the library.

numberOfPointers

Represents the number of pointers (fingers) currently placed on the screen.

pointerType

Indicates the type of pointer device in use. This value is represented by the PointerType enum, which includes the following fields:

- TOUCH - represents finger
-
STYLUS - represents stylus or digital pen
-
MOUSE - represents computer mouse
-
KEY - represents keyboard
-
OTHER - represents unknown device type that is not relevant

---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Gestures/ Shared/Gesture Detector Functional1 (packages/docs-gesture-handler/versioned_docs/version-2.x/gestures/_shared/gesture-detector-functional1.md)

jsx
export default function Example() {
const tap = Gesture.Tap().onStart(() => {
console.log('tap');
});

return (
<GestureDetector gesture={tap}>
<FunctionalComponent>
<View style={styles.box} />
</FunctionalComponent>
</GestureDetector>
);
}

function FunctionalComponent(props) {
return <View collapsable={false}>{props.children}</View>;
}

text
---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Guides/ Category .Json (packages/docs-gesture-handler/versioned_docs/version-2.x/guides/_category_.json)

{
"label": "Guides",
"position": 2,
"link": {
"type": "generated-index"
}
}

---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Guides/Migrating Off Rnghenabledroot (packages/docs-gesture-handler/versioned_docs/version-2.x/guides/migrating-off-rnghenabledroot.md)

---
id: migrating-off-rnghenabledroot
title: Migrating off RNGHEnabledRootView
---

Update MainActivity.java

Update your MainActivity.java file (or wherever you create an instance of ReactActivityDelegate), so that it no longer overrides the method responsible for creating ReactRootView instance, or modify it so that it no longer uses RNGestureHandlerEnabledRootView. Do not forget to remove import for RNGestureHandlerEnabledRootView:

java
package com.swmansion.gesturehandler.react.example;

import com.facebook.react.ReactActivity;
- import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;
public class MainActivity extends ReactActivity {

- @Override
- protected ReactActivityDelegate createReactActivityDelegate() {
- return new ReactActivityDelegate(this, getMainComponentName()) {
- @Override
- protected ReactRootView createRootView() {
- return new RNGestureHandlerEnabledRootView(MainActivity.this);
- }
- };
- }
}

text

Check if your app works correctly

Some libraries (for example React Navigation) already use GestureHandlerRootView as a wrapper to enable gesture interactions. In that case you don't have to add one yourself. If gestures in your app work as expected after removing RNGestureHandlerEnabledRootView you can skip the next step.

Update your JS code

Instead of using RNGestureHandlerEnabledRootView wrap your entry point with <GestureHandlerRootView>, for example:

jsx
export default function App() {
return (
<GestureHandlerRootView style={{ flex: 1 }}>
{/ content /}
</GestureHandlerRootView>
);
}
text
:::info
Note that
GestureHandlerRootView acts like a normal View. So if you want it to fill the screen, you will need to pass { flex: 1 } like you'll need to do with a normal View. By default, it'll take the size of the content nested inside.
:::

---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Guides/Swipe And Scroll (packages/docs-gesture-handler/versioned_docs/version-2.x/guides/swipe-and-scroll.md)

---
id: swipe-and-scroll
title: Custom swipeable components inside ScrollView (web)
sidebar_position: 5
---

While we recommend using our own ReanimatedSwipeable component, creating your own version of swipeable gives you more control over its behavior. A common issue here is that after creating your own swipeable component, scroll does not work. In that case, try adding touchAction set to "pan-y", like this:

jsx
<GestureDetector gesture={...} ... touchAction="pan-y">
...
</GestureDetector>
text
---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Guides/Testing (packages/docs-gesture-handler/versioned_docs/version-2.x/guides/testing.md)

---
id: testing
title: Testing with Jest
sidebar_position: 4
---

Mocking native modules

In order to load mocks provided by RNGH add the following to your jest config in package.json:

json
"setupFiles": ["./node_modules/react-native-gesture-handler/jestSetup.js"]
text
Example:
json
"jest": {
"preset": "react-native",
"setupFiles": ["./node_modules/react-native-gesture-handler/jestSetup.js"]
}
text

Testing Gestures' and Gesture handlers' callbacks

RNGH provides an API for triggering selected handlers:

- fireGestureHandler(gestureOrHandler, eventList)
-
getByGestureTestId(testID)

fireGestureHandler(gestureOrHandler, eventList)

Simulates one event stream (i.e. event sequence starting with BEGIN state and ending
with one of
END/FAIL/CANCEL states), calling appropriate callbacks associated with given gesture handler.

Arguments

#### gestureOrHandler

Represents either:

1. Gesture handler component found by Jest queries (e.g. getByTestId)
2. Gesture found by
getByGestureTestId()

#### eventList

Event data passed to appropriate callback. RNGH fills event list if required
data is missing using these rules:

1. oldState is filled using state of the previous event. BEGIN events use
UNDETERMINED value as previous event.
2. Events after first
ACTIVE state can omit state field.
3. Handler specific data is filled (e.g.
numberOfTouches, x fields) with
defaults.
4. Missing
BEGIN and END events are added with data copied from first and last
passed event, respectively.
5. If the first event doesn't have a
state field, the ACTIVE state is assumed.

Some examples:

jsx
const oldStateFilled = [
{ state: State.BEGAN },
{ state: State.ACTIVE },
{ state: State.END },
]; // three events with specified state are fired.

const implicitActiveState = [
{ state: State.BEGAN },
{ state: State.ACTIVE },
{ x: 5 },
{ state: State.END },
]; // 4 events, including two ACTIVE events (second one has overridden additional data).

const implicitBegin = [
{ x: 1, y: 11 },
{ x: 2, y: 12, state: State.FAILED },
]; // 3 events, including implicit BEGAN, one ACTIVE, and one FAILED event with additional data.

const implicitBeginAndEnd = [
{ x: 5, y: 15 },
{ x: 6, y: 16 },
{ x: 7, y: 17 },
]; // 5 events, including 3 ACTIVE events and implicit BEGAN and END events. BEGAN uses first event's additional data, END uses last event's additional data.

const allImplicits = []; // 3 events, one BEGIN, one ACTIVE, one END with defaults.

text

Example

Extracted from RNGH tests, check Events.test.tsx for full implementation.

tsx
it('sends events with additional data to handlers', () => {
const panHandlers = mockedEventHandlers();
render(<SingleHandler handlers={panHandlers} treatStartAsUpdate />);
fireGestureHandler<PanGesture>(getByGestureTestId('pan'), [
{ state: State.BEGAN, translationX: 0 },
{ state: State.ACTIVE, translationX: 10 },
{ translationX: 20 },
{ translationX: 20 },
{ state: State.END, translationX: 30 },
]);

expect(panHandlers.active).toHaveBeenCalledTimes(3);
expect(panHandlers.active).toHaveBeenLastCalledWith(
expect.objectContaining({ translationX: 20 })
);
});

text

getByGestureTestId(testID)

Returns opaque data type associated with gesture. Gesture is found via testID attribute in rendered
components (see
withTestID method).

Arguments

#### testID

String identifying gesture.

Notes

testID must be unique among components rendered in test.

Example

See above example for fireGestureHandler.

---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Guides/Troubleshooting (packages/docs-gesture-handler/versioned_docs/version-2.x/guides/troubleshooting.md)

---
id: troubleshooting
title: Troubleshooting
sidebar_position: 3
---

Troubleshooting

Thanks for giving this library a try! We are sorry that you might have encountered issues though. Here is how you can seek help:

1. Search over the issues on Github. There is a chance someone had this problem in the past and it has been resolved!
2. When sure your problem hasn't been reported or was reported but the proposed solution doesn't work for you please follow our issue reporting guidelines.
3. You can try seeking help on Expo Developers Discord where we often hang out.
4. If you feel like reading the source code I highly recommend it, as this is by far the best resource and gives you the most up to date insights into how the library works and what might be causing the bug.
5. If you managed to find the solution consider contributing a fix or update our documentation to make this information easier to find for the others in the future.

Reporting issues

This library is maintained by a very small team.
Please be mindful of that when reporting an issue and when it happens that we can't get back to you as soon as you might expect.
We would love to fix all the problems as soon as possible, but often our time is constrained by other issues/features or projects.
To make it easier for us to understand your issue and to be able to approach it sooner you can help by:

- Making sure the issue description is complete. Please include all the details about your environment (library version, RN version, device OS etc).
- It is the best to provide an example app that reproduces the issue you are having. Put it up on gist, snack or create a repo on Github – it doesn't matter as long as we can easily pull it in, run and see the issue.
- Explain how you run your repro app and what steps to take to reproduce the issue.
- Isolate your issue from other dependencies you might be using and make the repro app as minimal as possible.
- If you have spent some time figuring out the root cause of the problem you can leave a note about your findings so far.
- Do not comment on closed issues. It is very unlikely that we are going to notice your comment in such a case. If the issue has been closed, but the proposed solution doesn't work for you, please open a new one providing all the information necessary and linking to the solution you have tried.

It's not a bug, it's a feature

- Changing enabled prop during a gesture has no effect, only when a gesture starts (that is a finger touches the screen) the enabled prop is taken into consideration to decide whether to extract (or not) the gesture and provide it with stream of events to analyze.
-
Native gesture may not conform to the standard state flow due to platform specific workarounds to incorporate native views into RNGH.
- Keep in mind that
Touchables from RNGH are rendering two additional views that may need to be styled separately to achieve desired effect (style and containerStyle props).
- In order for the gesture composition to work, all composed gestures must be attached to the same
GestureHandlerRootView.

Multiple instances of Gesture Handler were detected

This error usually happens when in your project there exists more than one instance of Gesture Handler. It can occur when some of your dependencies have installed Gesture Handler inside their own node_modules instead of using it as a peer dependency. In this case two different versions of Gesture Handler JS module try to install the same Native Module. You can resolve this problem manually by modifying your package.json file.

You can check which libraries are using Gesture Handler, for example, with the command:

bash
npm ls react-native-gesture-handler
yarn why react-native-gesture-handler
text
If you use yarn you should add resolution property.
json
"resolutions": {
"react-native-gesture-handler": <Gesture Handler version>
}
text
If you use npm you should add overrides property.
json
"overrides": {
"react-native-gesture-handler": <Gesture Handler version>
}
text
After that you need to run your package manager again.
bash
yarn
text
or
bash
npm install
text

Automatic workletization of gesture callbacks

Reanimated's Babel plugin is setup in a way that automatically marks callbacks passed to gestures in the configuration chain as worklets. This means that as long as all your callbacks are defined in a single chain, you don't need to add a 'worklet'; directive at the beginning of the functions. Here is an example that will be automatically workletized:

jsx
const gesture = Gesture.Tap().onBegin(() => {
console.log(_WORKLET);
});
text
And here are some examples that won't:
jsx
const gesture = Gesture.Tap();
gesture.onBegin(() => {
console.log(_WORKLET);
});
text
jsx
const callback = () => {
console.log(_WORKLET);
};
const gesture = Gesture.Tap().onBegin(callback);
text
jsx
const callback = () => {
console.log(_WORKLET);
};
const gesture = Gesture.Tap();
gesture.onBegin(callback);
text
In the above cases, you should add a "worklet"; directive at the beginning of the callbacks, like so:
jsx
const callback = () => {
// highlight-next-line
'worklet';
console.log(_WORKLET);
};
const gesture = Gesture.Tap().onBegin(callback);
text
jsx
const callback = () => {
// highlight-next-line
'worklet';
console.log(_WORKLET);
};
const gesture = Gesture.Tap();
gesture.onBegin(callback);
text
---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Guides/Upgrading To 2 (packages/docs-gesture-handler/versioned_docs/version-2.x/guides/upgrading-to-2.md)

---
id: upgrading-to-2
title: Upgrading to the new API introduced in Gesture Handler 2
---

Make sure to migrate off the RNGestureHandlerEnabledRootView (Android only)

Gesture Handler 1 required you to override createRootView to return an instance of RNGestureHandlerEnabledRootView. This class has been the cause of many hard-to-debug and hard-to-fix crashes and was deprecated in version 2.0, and subsequently removed in version 2.4. If you are still using it, check out migrating off RNGHEnabledRootView guide.

Upgrading to the new API

The most important change brought by the Gesture Handler 2 is the new Gesture API, along with the GestureDetector component. It makes declaring gestures easier, as it handles much of the work under the hood and reduces the amount of necessary boilerplate code. Instead of a separate component for every type of gesture, the GestureDetector component is used to attach gestures to the underlying view based on the configuration object passed to it. The configuration objects are created using the Gesture object, here is a simple example:

jsx
const tapGesture = Gesture.Tap().onStart(() => {
console.log('Tap!');
});
...
return (
<GestureDetector gesture={tapGesture}>
<View />
</GestureDetector>
);
text
As you can see, there are no onGestureEvent and onHandlerStateChange callbacks, instead the state machine is handled under the hood and relevant callbacks are called for specific transitions or events:

- onBegin - called when the gesture transitions to the BEGAN state, which in most cases is when the gesture starts processing the touch stream - when the finger first touches the view
-
onStart - called when the activation criteria for the gesture are met and it transitions from BEGAN to ACTIVE state
-
onUpdate - replaces onGestureEvent, called every time the gesture sends a new event while it's in the ACTIVE state
-
onChange - if defined, called just after onUpdate, the events passed to it are the same as the ones passed to onUpdate but they also contain change values which hold the change in value they represent since the last event (i.e. in case of the Pan gesture, the event will also contain changeX and changeY properties)
-
onEnd - called when the gesture transitions from the ACTIVE state to either of END, FAILED or CANCELLED - you can tell whether the gesture finished due to user interaction or because of other reason (like getting cancelled by the system, or failure criteria) using the second value passed to the onEnd callback alongside the event
-
onFinalize - called when the gesture transitions into either of END, FAILED or CANCELLED state, if the gesture was ACTIVE, onEnd will be called first (similarly to onEnd you can determine the reason for finishing using the second argument)

The difference between onEnd and onFinalize is that the onEnd will be called only if the gesture was ACTIVE, while onFinalize will be called if the gesture has BEGAN. This means that you can use onEnd to clean up after onStart, and onFinalize to clean up after onBegin (or both onBegin and onStart).

Configuring the gestures

The new gesture objects are configured in the builder-like pattern. Instead of properties, each gesture provides methods that allow for its customization. In most cases the names of the methods are the same as the relevant props, or at least very similar. For example:

jsx
return (
<TapGestureHandler
numberOfTaps={2}
maxDurationMs={500}
maxDelayMs={500}
maxDist={10}
onHandlerStateChange={({ nativeEvent }) => {
if (nativeEvent.state === State.ACTIVE) {
console.log('Tap!');
}
}}>
<View />
</TapGestureHandler>
);
text
Would have the same effect as:
jsx
const tapGesture = Gesture.Tap()
.numberOfTaps(2)
.maxDuration(500)
.maxDelay(500)
.maxDistance(10)
.onStart(() => {
console.log('Tap!');
});

return (
<GestureDetector gesture={tapGesture}>
<View />
</GestureDetector>
);

text
You can check the modifiers available to specific gestures in the API Reference under Gestures.

Using multiple gestures on the same view

Using the gesture handler components, if you wanted to have multiple gestures on one view, you would have to stack them on top of each other and, in case you wanted to use animations, add an Animated.View after each handler, resulting in a deep component tree, for example:

jsx
return (
<TapGestureHandler ... >
<Animated.View>
<PanGestureHandler ... >
<Animated.View>
<PinchGestureHandler ... >
<YourView />
</PinchGestureHandler>
</Animated.View>
</PanGestureHandler>
</Animated.View>
</TapGestureHandler>
);
text
With the GestureDetector you can use the Gesture Composition API to stack the gestures onto one view:
jsx
const tapGesture = Gesture.Tap();
const panGesture = Gesture.Pan();
const pinchGesture = Gesture.Pinch();

return (
<GestureDetector gesture={Gesture.Race(tapGesture, panGesture, pinchGesture)}>
<YourView />
</GestureDetector>
);

text
Similarly, you can use Gesture.Simultaneous to replace stacked gesture handlers that should be able to recognize gestures simultaneously, and Gesture.Exclusive to replace stacked gesture handlers that require failure of others.

Replacing waitFor and simultaneousHandlers

If you want to make relations between the gestures attached to the same view, you should use the Gesture Composition API described above. However, if you want to make a relation between gestures attached to different views, or between gesture and an old gesture handler, you should use simultaneousWithExternalGesture instead of simultaneousHandlers, and requireExternalGestureToFail instead of waitFor. In case you need a ref object to pass to an old gesture handler, you can set it to the gesture using .withRef(refObject) modifier.

---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Guides/Manual Gestures/Index (packages/docs-gesture-handler/versioned_docs/version-2.x/guides/manual-gestures/index.md)

---
id: manual-gestures
title: Manual gestures
sidebar_label: Manual gestures
sidebar_position: 2
---

import Step, { Divider } from '@site/src/theme/Step';
import Step1 from './\_steps/step1.md';
import Step2 from './\_steps/step2.md';
import Step3 from './\_steps/step3.md';
import Step4 from './\_steps/step4.md';
import Step5 from './\_steps/step5.md';
import Step6 from './\_steps/step6.md';
import Step7 from './\_steps/step7.md';

RNGH2 finally brings one of the most requested features: manual gestures and touch events. To demonstrate how to make a manual gesture we will make a simple one that tracks all pointers on the screen.

<Step title="Step 1">
First, we need a way to store information about the pointer: whether it should be visible and its position.
<Step1 />
</Step>

<Step title="Step 2">
We also need a component to mark where a pointer is. In order to accomplish that we will make a component that accepts two shared values: one holding information about the pointer using the interface we just created, the other holding a bool indicating whether the gesture has activated.
In this example when the gesture is not active, the ball representing it will be blue and when it is active the ball will be red and slightly bigger.
<Step2 />
</Step>

<Step title="Step 3">
Now we have to make a component that will handle the gesture and draw all the pointer indicators. We will store data about pointers in an array of size 12, as that is the maximum number of touches that RNGH will track, and render them inside an Animated.View.
<Step3 />
</Step>

<Step title="Step 4">
We have our components set up and we can finally get to making the gesture! We will start with onTouchesDown where we need to set the position of the pointers and make them visible. We can get this information from the touches property of the event. In this case we will also check how many pointers are on the screen and activate the gesture if there are at least two.
<Step4 />
</Step>

<Step title="Step 5">
Next, we will handle pointer movement. In onTouchesMove we will simply update the position of moved pointers.
<Step5 />
</Step>

<Step title="Step 6">
We also need to handle lifting fingers from the screen, which corresponds to onTouchesUp. Here we will just hide the pointers that were lifted and end the gesture if there are no more pointers on the screen.
Note that we are not handling onTouchesCancelled as in this very basic case we don't expect it to happen, however you should clear data about cancelled pointers (most of the time all active ones) when it is called.
<Step6 />
</Step>

<Step title="Step 7">
Now that our pointers are being tracked correctly and we have the state management, we can handle activation and ending of the gesture. In our case, we will simply set the active shared value either to true or false.
<Step7 />
</Step>

And that's all! As you can see using manual gestures is really easy but as you can imagine, manual gestures are a powerful tool that makes it possible to accomplish things that were previously impossible with RNGH.

Modifying existing gestures

While manual gestures open great possibilities we are aware that reimplementing pinch or rotation from scratch just because you need to activate in specific circumstances or require position of the fingers, would be a waste of time as those gestures are already available. Therefore, you can use touch events with every gesture to extract more detailed information about the gesture than what the basic events alone provide. We also added a manualActivation modifier on all continuous gestures, which prevents the gesture it is applied to from activating automatically, giving you full control over its behavior.

This functionality makes another highly requested feature possible: drag after long press. Simply set manualActivation to true on a PanGesture and use StateManager to fail the gesture if the user attempts to drag the component sooner than the duration of the long press.

---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Guides/Manual Gestures/ Steps/Step1 (packages/docs-gesture-handler/versioned_docs/version-2.x/guides/manual-gestures/_steps/step1.md)

jsx
interface Pointer {
visible: boolean;
x: number;
y: number;
}
text
---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Guides/Manual Gestures/ Steps/Step2 (packages/docs-gesture-handler/versioned_docs/version-2.x/guides/manual-gestures/_steps/step2.md)

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

function PointerElement(props: {
pointer: Animated.SharedValue<Pointer>,
active: Animated.SharedValue<boolean>,
}) {
const animatedStyle = useAnimatedStyle(() => ({
transform: [
{ translateX: props.pointer.value.x },
{ translateY: props.pointer.value.y },
{
scale:
(props.pointer.value.visible ? 1 : 0) *
(props.active.value ? 1.3 : 1),
},
],
backgroundColor: props.active.value ? 'red' : 'blue',
}));

return <Animated.View style={[styles.pointer, animatedStyle]} />;
}

// ...

const styles = StyleSheet.create({
pointer: {
width: 60,
height: 60,
borderRadius: 30,
backgroundColor: 'red',
position: 'absolute',
marginStart: -30,
marginTop: -30,
},
});

text
---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Guides/Manual Gestures/ Steps/Step3 (packages/docs-gesture-handler/versioned_docs/version-2.x/guides/manual-gestures/_steps/step3.md)

jsx
import { Gesture, GestureDetector } from 'react-native-gesture-handler';

export default function Example() {
const trackedPointers: Animated.SharedValue<Pointer>[] = [];
const active = useSharedValue(false);

for (let i = 0; i < 12; i++) {
trackedPointers[i] =
useSharedValue <
Pointer >
{
visible: false,
x: 0,
y: 0,
};
}

const gesture = Gesture.Manual();

return (
<GestureDetector gesture={gesture}>
<Animated.View style={{ flex: 1 }}>
{trackedPointers.map((pointer, index) => (
<PointerElement pointer={pointer} active={active} key={index} />
))}
</Animated.View>
</GestureDetector>
);
}

text
---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Guides/Manual Gestures/ Steps/Step4 (packages/docs-gesture-handler/versioned_docs/version-2.x/guides/manual-gestures/_steps/step4.md)

jsx {2-15}
const gesture = Gesture.Manual().onTouchesDown((e, manager) => {
for (const touch of e.changedTouches) {
trackedPointers[touch.id].value = {
visible: true,
x: touch.x,
y: touch.y,
};
}

if (e.numberOfTouches >= 2) {
manager.activate();
}
});

text
---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Guides/Manual Gestures/ Steps/Step5 (packages/docs-gesture-handler/versioned_docs/version-2.x/guides/manual-gestures/_steps/step5.md)

jsx {3-12}
const gesture = Gesture.Manual()
...
.onTouchesMove((e, _manager) => {
for (const touch of e.changedTouches) {
trackedPointers[touch.id].value = {
visible: true,
x: touch.x,
y: touch.y,
};
}
})
text
---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Guides/Manual Gestures/ Steps/Step6 (packages/docs-gesture-handler/versioned_docs/version-2.x/guides/manual-gestures/_steps/step6.md)

jsx {3-16}
const gesture = Gesture.Manual()
...
.onTouchesUp((e, manager) => {
for (const touch of e.changedTouches) {
trackedPointers[touch.id].value = {
visible: false,
x: touch.x,
y: touch.y,
};
}

if (e.numberOfTouches === 0) {
manager.end();
}
})

text
---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Guides/Manual Gestures/ Steps/Step7 (packages/docs-gesture-handler/versioned_docs/version-2.x/guides/manual-gestures/_steps/step7.md)

jsx {3-10}
const gesture = Gesture.Manual()
...
.onStart(() => {
active.value = true;
})
.onEnd(() => {
active.value = false;
});
text
---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Guides/Quickstart/Index (packages/docs-gesture-handler/versioned_docs/version-2.x/guides/quickstart/index.md)

---
id: quickstart
title: Quick start
sidebar_label: Quick start
sidebar_position: 1
---

import Step, { Divider } from '@site/src/theme/Step';
import Step1 from './\_steps/step1.md';
import Step2 from './\_steps/step2.md';
import Step3 from './\_steps/step3.md';
import Step4 from './\_steps/step4.md';
import Step5 from './\_steps/step5.md';

RNGH2 provides a much simpler way to add gestures to your app. All you need to do is wrap the view that you want your gesture to work on with GestureDetector, define the gesture and pass it to detector. That's all!

To demonstrate how you would use the new API, let's make a simple app where you can drag a ball around. You will need to add react-native-gesture-handler (for gestures) and react-native-reanimated (for animations) modules.

<Step title="Step 1">
<div>First let's define styles we will need to make the app:</div>
<Step1 />
</Step>

<Step title="Step 2">
<div>Then we can start writing our <code>Ball</code> component:</div>
<Step2 />
</Step>

<Step title="Step 3">
<div>
We also need to define{' '}
<a href="https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/glossary#shared-value">
shared values
</a>{' '}
to keep track of the ball position and create animated styles in order to be
able to position the ball on the screen:
</div>
<Step3 />
</Step>

<Step title="Step 4">
<div>And add it to the ball's styles:</div>
<Step4 />
</Step>

<Step title="Step 5">
<div>
The only thing left is to define the pan gesture and assign it to the
detector:
</div>
<Step5 />
</Step>

Note the start shared value. We need it to store the position of the ball at the moment we grab it to be able to correctly position it later, because we only have access to translation relative to the starting point of the gesture.

Now you can just add Ball component to some view in the app and see the results! (Or you can just check the code here and see it in action in the Example app.)

---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Guides/Quickstart/ Steps/Step1 (packages/docs-gesture-handler/versioned_docs/version-2.x/guides/quickstart/_steps/step1.md)

jsx
import { StyleSheet } from 'react-native';

const styles = StyleSheet.create({
ball: {
width: 100,
height: 100,
borderRadius: 100,
backgroundColor: 'blue',
alignSelf: 'center',
},
});

text
---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Guides/Quickstart/ Steps/Step2 (packages/docs-gesture-handler/versioned_docs/version-2.x/guides/quickstart/_steps/step2.md)

jsx
import { GestureDetector } from 'react-native-gesture-handler';
import Animated from 'react-native-reanimated';

function Ball() {
return (
<GestureDetector>
<Animated.View style={[styles.ball]} />
</GestureDetector>
);
}

text
---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Guides/Quickstart/ Steps/Step3 (packages/docs-gesture-handler/versioned_docs/version-2.x/guides/quickstart/_steps/step3.md)

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

function Ball() {
const isPressed = useSharedValue(false);
const offset = useSharedValue({ x: 0, y: 0 });

const animatedStyles = useAnimatedStyle(() => {
return {
transform: [
{ translateX: offset.value.x },
{ translateY: offset.value.y },
{ scale: withSpring(isPressed.value ? 1.2 : 1) },
],
backgroundColor: isPressed.value ? 'yellow' : 'blue',
};
});

// ...
}

text
---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Guides/Quickstart/ Steps/Step4 (packages/docs-gesture-handler/versioned_docs/version-2.x/guides/quickstart/_steps/step4.md)

jsx {4}
// ...
return (
<GestureDetector>
<Animated.View style={[styles.ball, animatedStyles]} />
</GestureDetector>
);
// ...
text
---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Guides/Quickstart/ Steps/Step5 (packages/docs-gesture-handler/versioned_docs/version-2.x/guides/quickstart/_steps/step5.md)

jsx
import { Gesture } from 'react-native-gesture-handler';

function Ball() {
// ...
const start = useSharedValue({ x: 0, y: 0 });
const gesture = Gesture.Pan()
.onBegin(() => {
isPressed.value = true;
})
.onUpdate((e) => {
offset.value = {
x: e.translationX + start.value.x,
y: e.translationY + start.value.y,
};
})
.onEnd(() => {
start.value = {
x: offset.value.x,
y: offset.value.y,
};
})
.onFinalize(() => {
isPressed.value = false;
});
// ...
}

text
jsx {3}
// ...
return (
<GestureDetector gesture={gesture}>
<Animated.View style={[styles.ball, animatedStyles]} />
</GestureDetector>
);
// ...
text
---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Under The Hood/ Category .Json (packages/docs-gesture-handler/versioned_docs/version-2.x/under-the-hood/_category_.json)

{
"label": "Under the hood",
"position": 5,
"link": {
"type": "generated-index"
}
}

---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Under The Hood/How Does It Work (packages/docs-gesture-handler/versioned_docs/version-2.x/under-the-hood/how-does-it-work.md)

---
id: how-does-it-work
title: How does it work?
sidebar_label: How does it work?
---

Units

All handler component properties and event attributes that represent onscreen dimensions are expressed in screen density independent units we refer to as "points".
These are the units commonly used in React Native ecosystem (e.g. in the layout system).
They do not map directly to physical pixels but instead to iOS's points and to dp units on Android.

iOS

All gestures are implemented using UIGestureRecognizers, some of them have been slightly modified to allow for more customization and to conform to the state flow of RNGH. When you assign a gesture configuration to the GestureDetector, it creates all the required recognizers and assigns them to the child view of the detector. From this point most of the heavy lifting is handled by the UIKit (with our help to correctly implement interactions between gestures).

Android

Unfortunately, Android doesn't provide an easy way of handling gestures hence most of them were implemented from scratch, including a system for managing how the gestures should interact with each other. Here's a quick overview of how it works:
When you wrap a component with
GestureHandlerRootView it allows for the RNGH to intercept all touch events on that component and process them, deciding whether they should be handled by one of the gesture handlers or passed to the underlying view. Gesture handlers are created when you assign a gesture configuration to the GestureDetector, it initializes all of the necessary handlers natively. Every GestureHandlerRootView also has a specific handler to decide whether to pass the touch events or to consume them. It can never activate, only begin, end or be cancelled. When this handler is in the UNDETERMINED state it means that there is no touch in progress, however when the touch starts it transitions to the BEGAN state. As long as it stays in that state, no touch event is consumed, but as soon as it gets cancelled (meaning that some handler has activated) all incoming touch events get consumed, preventing underlying view from receiving them.

When a pointer touches the screen the view tree is traversed in order to extract all handlers attached to the views below the finger (including the one attached to the GestureHandlerRootView) and all extracted handlers transition to the BEGAN state, signalling that the gesture may have begun. The touch events continue to be delivered to all extracted handlers until one of them recognizes the gesture and tries to activate. At this point the orchestrator checks whether this gesture should wait for any other of the extracted gestures to fail. If it does, it's put to the waiting list, if it doesn't, it gets activated and all other gestures (that are not simultaneous with it) get cancelled. When a gesture handler transitions to a finished state (the gesture recognized by it stops, it fails or gets cancelled) the orchestrator checks the waiting handlers. Every one of them that waited for the gesture that just failed tries to activate again (and again the orchestrator checks if it should wait for any of the extracted gestures...).

---

Packages/Docs Gesture Handler/Versioned Docs/Version 2.X/Under The Hood/State (packages/docs-gesture-handler/versioned_docs/version-2.x/under-the-hood/state.md)

---
id: state
title: Handler State
sidebar_label: Handler State
---

As described in "About Gesture Handlers", gesture handlers can be treated as "state machines".
At any given time, each handler instance has an assigned state that can change when new touch events occur or can be forced to change by the touch system in certain circumstances.

A gesture handler can be in one of the six possible states:

- Accessing state
- State flows
- States
- UNDETERMINED
- FAILED
- BEGAN
- CANCELLED
- ACTIVE
- END

Each state has its own description below.

Accessing state

We can monitor a handler's state changes by using the onHandlerStateChange callback and the destructured nativeEvent argument passed to it.
This can be done by comparing the
nativeEvent's state attribute to one of the constants exported under the State object (see example below).

jsx
import { State, LongPressGestureHandler } from 'react-native-gesture-handler';

class Demo extends Component {
_handleStateChange = ({ nativeEvent }) => {
if (nativeEvent.state === State.ACTIVE) {
Alert.alert('Longpress');
}
};
render() {
return (
<LongPressGestureHandler onHandlerStateChange={this._handleStateChange}>
<Text style={styles.buttonText}>Longpress me</Text>
</LongPressGestureHandler>
);
}
}

text

State flows

The most typical flow of state is when a gesture handler picks up on an initial touch event then recognizes it then acknowledges its ending then resets itself back to the initial state.

The flow looks as follows (longer arrows represent that there are possibly more touch events received before the state changes):

UNDETERMINED -> BEGAN ------> ACTIVE ------> END -> UNDETERMINED

Another possible flow is when a handler receives touches that cause a recognition failure:

UNDETERMINED -> BEGAN ------> FAILED -> UNDETERMINED

At last, when a handler does properly recognize the gesture but then is interrupted by the touch system. In that case, the gesture recognition is canceled and the flow looks as follows:

UNDETERMINED -> BEGAN ------> ACTIVE ------> CANCELLED -> UNDETERMINED

States

The section below describes all possible handler states:

UNDETERMINED

This is the initial state of each handler and it goes into this state after it's done recognizing a gesture.

FAILED

A handler received some touches but for some reason didn't recognize them. For example, if a finger travels more distance than a defined maxDist property allows, then the handler won't become active but will fail instead. Afterwards, its state will be reset to UNDETERMINED.

BEGAN

Handler has started receiving touch stream but hasn't yet received enough data to either fail or activate.

CANCELLED

The gesture recognizer has received a signal (possibly new touches or a command from the touch system controller) resulting in the cancellation of a continuous gesture. The gesture's state will become CANCELLED until it is finally reset to the initial state, UNDETERMINED.

ACTIVE

Handler has recognized a gesture. It will become and stay in the ACTIVE state until the gesture finishes (e.g. when user lifts the finger) or gets cancelled by the touch system. Under normal circumstances the state will then turn into END. In the case that a gesture is cancelled by the touch system, its state would then become CANCELLED.
Learn about discrete and continuous handlers here to understand how long a handler can be kept in the
ACTIVE state.

END

The gesture recognizer has received touches signalling the end of a gesture. Its state will become END until it is reset to UNDETERMINED.

---

Packages/Docs Gesture Handler/Versioned Sidebars/Version 1.X Sidebars.Json (packages/docs-gesture-handler/versioned_sidebars/version-1.x-sidebars.json)

{
"docs": [
{
"collapsed": true,
"type": "category",
"label": "Basics",
"items": [
{
"type": "doc",
"id": "getting-started"
},
{
"type": "doc",
"id": "about-handlers"
},
{
"type": "doc",
"id": "state"
},
{
"type": "doc",
"id": "interactions"
},
{
"type": "doc",
"id": "example"
}
]
},
{
"collapsed": true,
"type": "category",
"label": "API reference",
"items": [
{
"collapsed": true,
"type": "category",
"label": "Gesture handlers",
"items": [
{
"type": "doc",
"id": "api/gesture-handlers/common-gh"
},
{
"type": "doc",
"id": "api/gesture-handlers/pan-gh"
},
{
"type": "doc",
"id": "api/gesture-handlers/tap-gh"
},
{
"type": "doc",
"id": "api/gesture-handlers/longpress-gh"
},
{
"type": "doc",
"id": "api/gesture-handlers/rotation-gh"
},
{
"type": "doc",
"id": "api/gesture-handlers/fling-gh"
},
{
"type": "doc",
"id": "api/gesture-handlers/pinch-gh"
},
{
"type": "doc",
"id": "api/gesture-handlers/force-gh"
},
{
"type": "doc",
"id": "api/gesture-handlers/nativeview-gh"
},
{
"type": "doc",
"id": "api/gesture-handlers/create-native-wrapper"
}
]
},
{
"collapsed": true,
"type": "category",
"label": "Components",
"items": [
{
"type": "doc",
"id": "api/components/buttons"
},
{
"type": "doc",
"id": "api/components/swipeable"
},
{
"type": "doc",
"id": "api/components/touchables"
},
{
"type": "doc",
"id": "api/components/drawer-layout"
}
]
}
]
},
{
"collapsed": true,
"type": "category",
"label": "Other",
"items": [
{
"type": "doc",
"id": "contributing"
},
{
"type": "doc",
"id": "troubleshooting"
},
{
"type": "doc",
"id": "resources"
},
{
"type": "doc",
"id": "credits"
}
]
}
]
}

---

Packages/Docs Gesture Handler/Versioned Sidebars/Version 2.X Sidebars.Json (packages/docs-gesture-handler/versioned_sidebars/version-2.x-sidebars.json)

{
"tutorialSidebar": [
{
"type": "autogenerated",
"dirName": "."
}
]
}

---

Packages/React Native Gesture Handler/Compatibility.Json (packages/react-native-gesture-handler/compatibility.json)

{
"3.2.x": {
"react-native": ["0.83", "0.84", "0.85", "0.86", "0.87"]
},
"3.1.x": {
"react-native": ["0.83", "0.84", "0.85", "0.86"]
},
"3.0.x": {
"react-native": ["0.82", "0.83", "0.84", "0.85", "0.86"]
}
}

---

Packages/React Native Gesture Handler/Package.Json (packages/react-native-gesture-handler/package.json)

{
"name": "react-native-gesture-handler",
"version": "2.29.0",
"description": "Declarative API exposing native platform touch and gesture system to React Native",
"scripts": {
"test": "jest",
"build": "yarn tsc -p tsconfig.build.json && bob build",
"ts-check": "yarn tsc --noEmit && yarn tsc -p __typetests__ --noEmit",
"format-js": "prettier --write --list-different './src//*.{js,jsx,ts,tsx}'",
"format:js": "yarn format-js",
"format:android": "node ../../scripts/format-android.js",
"format:apple": "FORMAT_GLOB_PATTERN=\"../packages/react-native-gesture-handler/apple//*.{h,m,mm,cpp}\" node ../../scripts/format-cpp.js",
"format:cpp": "FORMAT_GLOB_PATTERN=\"../packages/react-native-gesture-handler/{shared,android/src}//*.{h,cpp}\" node ../../scripts/format-cpp.js",
"lint-js": "eslint --ext '.js,.ts,.tsx' src/ && yarn prettier --check './src//*.{js,jsx,ts,tsx}'",
"lint:js": "yarn lint-js",
"lint:android": "./android/gradlew -p android spotlessCheck -q",
"circular-dependency-check": "yarn madge --extensions js,ts,tsx --circular src",
"clean": "rm -rf node_modules android/build android/.cxx",
"prepack": "cp ../../README.md ./README.md",
"postpack": "rm ./README.md"
},
"react-native": "src/index.ts",
"main": "lib/module/index.js",
"module": "lib/module/index.js",
"types": "lib/typescript/index.d.ts",
"files": [
"src",
"lib",
"!/__tests__",
"!/__fixtures__",
"!/__mocks__",
"android/build.gradle",
"android/gradle.properties",
"android/CMakeLists.txt",
"android/src/main/AndroidManifest.xml",
"android/src/main/java/",
"android/src/main/jni/",
"android/common/src/main/java/",
"android/reanimated/src/main/java/",
"android/noreanimated/src/main/java/",
"android/svg",
"android/nosvg",
"apple/",
"shared/",
"scripts/",
"ReanimatedSwipeable/",
"jest-utils/",
"ReanimatedDrawerLayout/",
"README.md",
"jestSetup.js",
"RNGestureHandler.podspec",
"react-native.config.js"
],
"repository": {
"type": "git",
"url": "git+https://github.com/software-mansion/react-native-gesture-handler.git"
},
"author": {
"email": "[email protected]",
"name": "Krzysztof Magiera"
},
"license": "MIT",
"readmeFilename": "README.md",
"bugs": {
"url": "https://github.com/software-mansion/react-native-gesture-handler/issues"
},
"homepage": "https://docs.swmansion.com/react-native-gesture-handler/",
"dependencies": {
"@types/react-test-renderer": "^19.1.0",
"invariant": "^2.2.4"
},
"devDependencies": {
"@babel/core": "^7.25.2",
"@babel/preset-env": "^7.25.3",
"@babel/preset-typescript": "^7.12.7",
"@react-native/babel-preset": "0.87.0",
"@react-native/jest-preset": "0.87.0",
"@testing-library/react-native": "^12.5.1",
"@types/invariant": "^2.2.37",
"@types/jest": "^29.5.12",
"@types/react": "^19.2.0",
"@typescript-eslint/eslint-plugin": "^6.9.0",
"@typescript-eslint/parser": "^6.9.0",
"babel-plugin-module-resolver": "^5.0.2",
"clang-format": "^1.8.0",
"eslint": "^8.57.0",
"eslint-config-satya164": "3.3.0",
"eslint-import-resolver-babel-module": "^5.2.0",
"eslint-plugin-jest": "27.4.3",
"eslint-plugin-prettier": "^5.0.1",
"eslint-plugin-react": "^7.37.5",
"husky": "^8.0.1",
"jest": "^29.7.0",
"lint-staged": "^12.3.2",
"madge": "^6.1.0",
"prettier": "3.3.3",
"react": "19.2.3",
"react-native": "0.87.0",
"react-native-builder-bob": "^0.40.13",
"react-native-reanimated": "4.6.0-nightly-20260811-248dee712",
"react-native-worklets": "patch:react-native-worklets@npm%3A0.12.0-nightly-20260810-fb9cb5596#~/.yarn/patches/react-native-worklets-npm-0.12.0-nightly-20260810-fb9cb5596-f08a72b88e.patch",
"react-test-renderer": "19.2.3",
"typescript": "~6.0.3"
},
"peerDependencies": {
"react": "*",
"react-native": "*"
},
"react-native-builder-bob": {
"source": "src",
"output": "lib",
"targets": [
"module",
[
"typescript",
{
"project": "tsconfig.build.json"
}
]
]
},
"codegenConfig": {
"name": "rngesturehandler_codegen",
"type": "all",
"jsSrcsDir": "./src/specs",
"android": {
"javaPackageName": "com.swmansion.gesturehandler"
},
"ios": {
"componentProvider": {
"RNGestureHandlerButton": "RNGestureHandlerButtonComponentView",
"RNGestureHandlerDetector": "RNGestureHandlerDetector"
}
}
},
"packageManager": "[email protected]"
}

---

Packages/React Native Gesture Handler/Tsconfig.Build.Json (packages/react-native-gesture-handler/tsconfig.build.json)

{
"extends": "./tsconfig.json",
"compilerOptions": {
"rootDir": "./src"
},
"exclude": ["/.test.ts", "/.test.tsx"]
}

---

Packages/React Native Gesture Handler/Tsconfig.Json (packages/react-native-gesture-handler/tsconfig.json)

{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "lib/typescript",
"exactOptionalPropertyTypes": true,
"paths": {
"react-native-gesture-handler": ["./src"]
},
"types": ["./src/global.d.ts", "jest"]
},
"include": ["src//.ts", "src//.tsx", "jestSetup.js"]
}

---

Packages/React Native Gesture Handler/ Typetests /Tsconfig.Json (packages/react-native-gesture-handler/__typetests__/tsconfig.json)

{
"extends": "../tsconfig.json",
"compilerOptions": {
"types": ["../src/global.d.ts", "jest"],
"rootDir": "..",
"noUnusedLocals": false
},
"include": ["."]
}

---

Packages/React Native Gesture Handler/Android/CMakeLists (packages/react-native-gesture-handler/android/CMakeLists.txt)

cmake_minimum_required(VERSION 3.13)
project(GestureHandlerCodegen)

set(CMAKE_VERBOSE_MAKEFILE on)
set(CMAKE_CXX_STANDARD 20)

file(GLOB_RECURSE rn_gesture_handler_SRCS CONFIGURE_DEPENDS ../shared/shadowNodes/*.cpp)
file(GLOB_RECURSE rn_gesture_handler_codegen_SRCS CONFIGURE_DEPENDS ./build/generated/source/codegen/jni/*.cpp)

add_library(
react_codegen_rngesturehandler_codegen
SHARED
${rn_gesture_handler_SRCS}
${rn_gesture_handler_codegen_SRCS}
)

if(ReactAndroid_VERSION_MINOR GREATER_EQUAL 80)
target_compile_reactnative_options(react_codegen_rngesturehandler_codegen PRIVATE)
endif()

target_include_directories(react_codegen_rngesturehandler_codegen PUBLIC ../shared/shadowNodes)
target_include_directories(react_codegen_rngesturehandler_codegen PUBLIC ./build/generated/source/codegen/jni)

target_link_libraries(
react_codegen_rngesturehandler_codegen
fbjni
jsi
reactnative
)

---

Packages/React Native Gesture Handler/Android/Src/Main/Jni/CMakeLists (packages/react-native-gesture-handler/android/src/main/jni/CMakeLists.txt)

project(GestureHandler)
cmake_minimum_required(VERSION 3.9.0)

string(
APPEND
CMAKE_CXX_FLAGS
" -DREACT_NATIVE_MINOR_VERSION=${REACT_NATIVE_MINOR_VERSION}")

set(CMAKE_VERBOSE_MAKEFILE ON)
if(${REACT_NATIVE_MINOR_VERSION} GREATER_EQUAL 73)
set(CMAKE_CXX_STANDARD 20)
else()
set(CMAKE_CXX_STANDARD 17)
endif()

set(PACKAGE_NAME "gesturehandler")
set(RNGH_DIR "${CMAKE_SOURCE_DIR}/../../../../")
set(REACT_ANDROID_DIR "${REACT_NATIVE_DIR}/ReactAndroid")

file(GLOB_RECURSE gesture_handler_SRCS CONFIGURE_DEPENDS ./*.cpp)
file(GLOB_RECURSE gesture_handler_shared_SRCS CONFIGURE_DEPENDS "${RNGH_DIR}/shared/runtime/*.cpp")

include(${REACT_ANDROID_DIR}/cmake-utils/folly-flags.cmake)
add_compile_options(${folly_FLAGS})

add_library(${PACKAGE_NAME}
SHARED
${gesture_handler_SRCS}
${gesture_handler_shared_SRCS}
)

target_include_directories(
${PACKAGE_NAME}
PUBLIC
"${CMAKE_SOURCE_DIR}"
"${RNGH_DIR}/shared/runtime"
PRIVATE
"${REACT_NATIVE_DIR}/ReactCommon"
)

if(ReactAndroid_VERSION_MINOR GREATER_EQUAL 80)
target_compile_reactnative_options(${LIB_TARGET_NAME} PRIVATE)
endif()

find_package(ReactAndroid REQUIRED CONFIG)
find_package(fbjni REQUIRED CONFIG)

target_link_libraries(
${PACKAGE_NAME}
ReactAndroid::reactnative
ReactAndroid::jsi
fbjni::fbjni
)

if(RNGH_USE_WORKLETS)
find_package(react-native-worklets REQUIRED CONFIG)
target_compile_definitions(${PACKAGE_NAME} PRIVATE RNGH_USE_WORKLETS=1)
target_link_libraries(${PACKAGE_NAME} react-native-worklets::worklets)
endif()

---

Packages/React Native Gesture Handler/Jest Utils/Package.Json (packages/react-native-gesture-handler/jest-utils/package.json)

{
"main": "../lib/module/jestUtils/index",
"module": "../lib/module/jestUtils/index",
"react-native": "../src/jestUtils/index",
"types": "../lib/typescript/jestUtils/index.d.ts"
}

---

Packages/React Native Gesture Handler/ReanimatedDrawerLayout/Package.Json (packages/react-native-gesture-handler/ReanimatedDrawerLayout/package.json)

{
"main": "../lib/module/components/ReanimatedDrawerLayout",
"module": "../lib/module/components/ReanimatedDrawerLayout",
"react-native": "../src/components/ReanimatedDrawerLayout",
"types": "../lib/typescript/components/ReanimatedDrawerLayout.d.ts"
}

---

Packages/React Native Gesture Handler/ReanimatedSwipeable/Package.Json (packages/react-native-gesture-handler/ReanimatedSwipeable/package.json)

{
"main": "../lib/module/components/ReanimatedSwipeable/",
"module": "../lib/module/components/ReanimatedSwipeable/",
"react-native": "../src/components/ReanimatedSwipeable/",
"types": "../lib/typescript/components/ReanimatedSwipeable/"
}

---

Skills/Gesture Handler 3 Migration/SKILL (skills/gesture-handler-3-migration/SKILL.md)

---
name: gesture-handler-3-migration
description: Migrates files containing React Native components which use the React Native Gesture Handler 2 API to Gesture Handler 3.
---

Migrate to Gesture Handler 3

This skill scans React Native components that use the Gesture Handler builder-based API and updates them to use the new hook-based API. It also updates related types and components to adapt to the new version.

When to Use

- Updating the usage of components imported from react-native-gesture-handler
- Upgrading to Gesture Handler 3
- Migrating to the new hook-based gesture API

Instructions

Use the instructions below to correctly replace all legacy APIs with the modern ones.

1. Identify all imports from 'react-native-gesture-handler'
2. For each
Gesture.X() call, replace with corresponding useXGesture() hook
3. Replace
Gesture import with imports for the used hooks
4. Convert builder method chains to configuration objects
5. Update callback names (onStart → onActivate, etc.)
6. Replace composed gestures with relation hooks. Keep rules of hooks in mind
7. Update GestureDetector usage if SVG is involved to Intercepting/Virtual GestureDetector
8. Update usage of compoenent imported from 'react-native-gesture-handler' according to "Legacy components" section

Migrating gestures

All hook gestures have their counterparts in the builder API: Gesture.X() becomes useXGesture(config). The methods are now config object fields with the same name as the relevant builder methods, unless specified otherwise.

The exception to thait is Gesture.ForceTouch which DOES NOT have a counterpart in the hook API.

#### Callback changes

In Gesture Handler 3 some of the callbacks were renamed, namely:

- onStart -> onActivate
-
onEnd -> onDeactivate
-
onTouchesCancelled -> onTouchesCancel

The onDeactivate and onFinalize callbacks no longer receive a second didSucceed/success boolean parameter. Instead, the event object now contains a canceled property. Note that the logic is inverted — canceled: true corresponds to the old success: false.

jsx
// Old (RNGH2)
.onEnd((event, success) => {
if (success) { / gesture succeeded / }
})

// New (RNGH3)
onDeactivate: (event) => {
if (!event.canceled) { / gesture succeeded / }
}

text
In the hooks API onChange is no longer available. Instead the change properties were moved to the event available inside onUpdate.

All callbacks of a gesture are now using the same type:

- usePanGesture() -> PanGestureEvent
-
useTapGesture() -> TapGestureEvent
-
useLongPressGesture() -> LongPressGestureEvent
-
useRotationGesture() -> RotationGestureEvent
-
usePinchGesture() -> PinchGestureEvent
-
useFlingGesture() -> FlingGestureEvent
-
useHoverGesture() -> HoverGestureEvent
-
useNativeGesture() -> RotationGestureEvent
-
useManualGesture() -> ManualGestureEvent

The exception to this is touch events:

- onTouchesDown
-
onTouchesUp
-
onTouchesMove
-
onTouchesCancel

Where each callback receives GestureTouchEvent regardless of the hook used.

#### StateManager

In Gesture Handler 3, stateManager is no longer passed to TouchEvent callbacks. Instead, you should use the global GestureStateManager.

GestureStateManager provides methods for imperative state management:

- .activate(handlerTag: number)
- .deactivate(handlerTag: number) (.end() in the old API)
- .fail(handlerTag: number)

handlerTag can be obtained in two ways:

1. From the gesture object returned by the hook (gesture.handlerTag)
2. From the event inside callback (
event.handlerTag)

Callback definitions CANNOT reference the gesture that's being defined. In this scenario use events to get access to the handler tag.

Remove GestureStateManager.begin() as gestures must now automatically enter the BEGAN state via touch events before they can be activated through the GestureStateManager.

Migrating relations

#### Composed gestures

Gesture.Simultaneous(gesture1, gesture2); becomes useSimultaneousGestures(pan1, pan2);

All relations from the old API and their counterparts in the new one:

- Gesture.Race() -> useCompetingGestures()
-
Gesture.Simultaneous() -> useSimultaneousGestures()
-
Gesture.Exclusive() -> useExclusiveGestures()

#### Cross components relations properties

Properties used to define cross-components interactions were renamed:

- .simultaneousWithExternalGesture -> simultaneousWith:
-
.requireExternalGestureToFail -> requireToFail:
-
.blocksExternalGesture -> block:

GestureDetector

The GestureDetector is a key component of react-native-gesture-handler. It supports gestures created either using the hooks API or the builder pattern (but those cannot be mixed, it's either or).

Don't use the same instance of a gesture across multiple Gesture Detectors as it will lead to an undefined behavior.

Integration with Reanimated

Worklets' Babel plugin is setup in a way that automatically marks callbacks passed to gestures in the configuration chain as worklets. This means that you don't need to add a 'worklet'; directive at the beginning of the functions.

This will not be workletized because the callback is defined outside of the gesture object:

jsx
const callback = () => {
console.log(_WORKLET);
};

const gesture = useTapGesture({
onBegin: callback,
});

text
The callback wrapped by any other higher order function will not be workletized:
jsx
const gesture = useTapGesture({
onBegin: useCallback(() => {
console.log(_WORKLET);
}, []),
});
text
In the above cases, you should add a "worklet"; directive as the first line of the callback.

Disabling Reanimated

Gestures created with the hook API have Reanimated integration enabled by default (if it's installed), meaning all callbacks are executed on the UI thread.

#### runOnJS

The runOnJS property allows you to dynamically control whether callbacks are executed on the JS thread or the UI thread. When set to true, callbacks will run on the JS thread. Setting it to false will execute them on the UI thread. Default value is false.

Migrating components relying on view hierarchy

Certain components, such as SVG, depend on the view hierarchy to function correctly. In Gesture Handler 3, GestureDetector disrupts these hierarchies. To resolve this issue, two new detectors have been introduced: InterceptingGestureDetector and VirtualGestureDetector.

InterceptingGestureDetector functions similarly to the GestureDetector, but it can also act as a proxy for VirtualGestureDetector within its component subtree. Because it can be used solely to establish the context for virtual detectors, the gesture property is optional.

VirtualGestureDetector is similar to the GestureDetector from RNGH2. Because it is not a host component, it does not interfere with the host view hierarchy. This allows you to attach gestures without disrupting functionality that depends on it.

Warning: VirtualGestureDetector has to be a descendant of InterceptingGestureDetector.

#### Migrating SVG

In Gesture Handler 2 it was possible to use GestureDetector directly on SVG. In Gesture Handler 3, the correct way to interact with SVG is to use InterceptingGestureDetector and VirtualGestureDetector.

Legacy components

When the code using the component relies on the APIs that are no longer available on the components in Gesture Handler 3 (like waitFor, simultaneousWith, blocksHandler, onHandlerStateChange, onGestureEvent props), it cannot be easily migrated in isolation. In this case update the imports to the Legacy version of the component, and inform the user that the dependencies need to be migrated first.

If the migration is possible, use the ask questions tool to clarify the user intent unless clearly stated beforehand: should the components be using the new implementation (no Legacy prefix when imported), or should they revert to the old implementation (Legacy prefix when imported)?

Don't suggest replacing buttons from Gesture Handler with components from React Native and vice versa.

The implementation of buttons has been updated, resolving most button-related issues. They have also been internally rewritten to utilize the new hook API. The legacy JS implementations of button components are still accessible but have been renamed with the prefix Legacy, e.g., RectButton is now available as LegacyRectButton. Those still use the new native component under the hood.

PureNativeButton has been removed. If encountered, inform the user that it has been removed and let them decide how to handle that case. They can achieve similar functionality with other buttons.

ReanimatedSwipeable prop dragOffsetFromRight now accepts negative values. If it was used with positive values, make sure to change the sign.

Other components have also been internally rewritten using the new hook API but are exported under their original names, so no changes are necessary on your part. However, if you need to use the previous implementation for any reason, the legacy components are also available and are prefixed with Legacy, e.g., ScrollView is now available as LegacyScrollView.

Rename all instances of createNativeWrapper to legacy_createNativeWrapper. This includes both the import statements and the function calls.

#### Migrating to Touchable

In Gesture Handler 3 the Touchable component replaces both the old buttons (BaseButton, RectButton, BorderlessButton) and the legacy core-style touchables (TouchableOpacity, TouchableHighlight, TouchableWithoutFeedback, TouchableNativeFeedback). It is a single component whose visual feedback is controlled entirely through props — pick the right combination instead of picking a different component.

The props you will use when migrating:

- onPress(event) — fired on a successful tap. Note: the callback signature changed; the old BaseButton.onPress received (pointerInside: boolean), Touchable.onPress receives a gesture event object instead.
-
onPressIn(event) / onPressOut(event) — fired when the pointer first touches and when it is released or leaves the component.
-
onLongPress() — fired after the press is held for delayLongPress milliseconds (default 600). When a long press fires, the subsequent release does not call onPress.
-
disabled — replaces the old enabled prop (note the inverted sense). Defaults to false.
-
cancelOnLeave — whether the press is cancelled when the pointer leaves the component bounds. Defaults to true. Use this to replace shouldCancelWhenOutside from raw buttons.
-
activeOpacity — opacity applied to the component itself while pressed (mirrors TouchableOpacity). Defaults to 1 (no opacity change).
-
underlayColor + activeUnderlayOpacity — color and opacity of the underlay shown while pressed (mirrors TouchableHighlight / RectButton). underlayColor defaults to 'transparent' and activeUnderlayOpacity to 0.105.
-
androidRipple — Android ripple config ({ color?, radius?, borderless?, foreground? }). When omitted, no native ripple is rendered. Use this to replace TouchableNativeFeedback.
-
animationDuration — press/hover animation timing in milliseconds. Pass a single number to apply it to every phase, or { in, out } (optionally with tap/hover/longPress overrides). Defaults to 50 in / 100 out.
-
hitSlop, testID, style, children — same as before.

##### Replacing Gesture Handler buttons

| Old component | Replace with (iOS / cross-platform default) |
| ----------------- | --------------------------------------------------------- |
|
BaseButton | <Touchable /> (default props) |
|
RectButton | <Touchable underlayColor="black" animationDuration={0} /> |
|
BorderlessButton| <Touchable activeOpacity={0.3} animationDuration={0} /> |

Android ripple: legacy RectButton/BorderlessButton use the native theme ripple on Android, while Touchable disables the ripple unless androidRipple is set. To preserve the legacy Android feedback, set androidRipple on Android instead of underlayColor/activeOpacity/animationDuration (don't combine them — the ripple is the visual feedback on Android). The two configs are different:

- RectButtonandroidRipple={{}}
-
BorderlessButtonandroidRipple={{ borderless: true }} (matches the legacy borderless ripple shape)

Use Platform.select to apply different props per platform. Example for RectButton:

jsx
import { Platform } from 'react-native';

<Touchable
{...Platform.select({
android: { androidRipple: {} },
default: { underlayColor: 'black', animationDuration: 0 },
})}
/>
`

##### Replacing legacy Touchables

| Old component | Replace with |
| --------------------------- | ---------------------------------------------------------------------------- |
|
TouchableOpacity | <Touchable activeOpacity={0.2} animationDuration={{ in: 0, out: 150 }} /> |
|
TouchableHighlight | <Touchable underlayColor={...} activeUnderlayOpacity={1} activeOpacity={...} /> — closest approximation only (not 1:1, see note below) |
|
TouchableWithoutFeedback | <Touchable /> (plain, no visual feedback props) |
|
TouchableNativeFeedback | <Touchable androidRipple={{ foreground: true }} /> (legacy default draws the ripple in the foreground; drop foreground if the original code passed useForeground={false}) |

For TouchableNativeFeedback, androidRipple must be set explicitly — without it no ripple is rendered. The legacy component defaults to useForeground: true, so { foreground: true } is the closest default replacement; omit foreground only when the original code set useForeground={false}. Add color, radius, or borderless if the original code customized the background prop.

For TouchableHighlight, a perfect 1:1 replacement is not possible — in the legacy component the container's own background becomes the underlay (solid underlayColor) and activeOpacity dims just the children on top, so the underlay shows through the dimmed children. Touchable instead has a separate underlay layer between the background and children, and its activeOpacity dims the whole component (background + underlay + children together). The closest approximation: carry underlayColor and activeOpacity over unchanged, and add activeUnderlayOpacity={1} so the underlay layer is rendered solid. Inform the user that the visual feedback may differ from the legacy component because of the different layering.

Do not swap Gesture Handler buttons/touchables for React Native core components or vice versa during migration — keep them within react-native-gesture-handler.

Replaced types

Most of the types used in the builder API, like TapGesture, are still present in Gesture Handler 3. However, they are now used in new hook API. Types for builder API now have Legacy prefix, e.g. TapGesture becomes LegacyTapGesture.

---

.Github/ISSUE TEMPLATE/Bug Report.Yml (.github/ISSUE_TEMPLATE/bug-report.yml)

name: Bug report
description: Report an issue with Gesture Handler here.
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report!

Before you proceed:

- Make sure to check whether there are similar issues in the repository
- Make sure to clean cache in your project. Depending on your setup this could be done by:
-
yarn start --reset-cache or
-
npm run start -- --reset-cache or
-
expo start --clear

- type: markdown
attributes:
value: |
## Required information
- type: textarea
id: description
attributes:
label: Description
description: Please provide a clear, concise and descriptive explanation of what the bug is. Include screenshots or a video if needed. Tell us what were you expecting to happen instead of what is happening now.
validations:
required: true

- type: textarea
id: steps-to-reproduce
attributes:
label: Steps to reproduce
description: Provide a detailed list of steps that reproduce the issue.
placeholder: |
1.
2.
3.
validations:
required: true

- type: input
id: repro
attributes:
label: A link to a Gist, an Expo Snack or a link to a repository based on this template that reproduces the bug.
description: |
Please provide code snippet, a Snack or a link to a repository on GitHub under your username that reproduces the issue.
Here are some tips for providing a minimal example: https://stackoverflow.com/help/mcve.
Issues without a reproduction are likely to stale.
placeholder: Link to a Snack or a GitHub repository
validations:
required: true

- type: input
id: gesture-handler-version
attributes:
label: Gesture Handler version
description: What version of react-native-gesture-handler are you using?
placeholder: 2.25.0
validations:
required: true

- type: input
id: react-native-version
attributes:
label: React Native version
description: What version of react-native are you using?
placeholder: 0.79.0
validations:
required: true

- type: dropdown
id: platforms
attributes:
label: Platforms
description: On what platform your application is running on?
multiple: true
options:
- Android
- iOS
- Web
- MacOS
validations:
required: true

- type: markdown
attributes:
value: |
## Additonal information

Providing as much information as possible greatly helps us with reproducting the issues.

- type: dropdown
id: runtime
attributes:
label: JavaScript runtime
description: What runtime is your application using?
options:
- Hermes
- JSC
- V8

- type: dropdown
id: workflow
attributes:
label: Workflow
description: How your application is managed? Not sure? Read this part of Expo documentation.
options:
- React Native (without Expo)
- Using Expo Go
- Using Expo Prebuild or an Expo development build

- type: dropdown
id: architecture
attributes:
label: Architecture
description: What React Native architecture your application is running on? Currently, the New Architecture is enabled by default for every new React Native project.
options:
- New Architecture (Fabric)
- Old Architecture (Paper)

- type: dropdown
id: build-type
attributes:
label: Build type
description: What mode your application is running?
options:
- Debug mode
- Release mode

- type: dropdown
id: emulator
attributes:
label: Device
description: How are you running your application?
options:
- iOS simulator
- Android emulator
- Real device

- type: input
id: device-model
attributes:
label: Device model
description: What device you are experiencing this problem on? Specify full device name along with the version of the operating system it's running.
placeholder: ex. Samsung Galaxy A22 (Android 12)

- type: dropdown
id: acknowledgements
attributes:
label: Acknowledgements
description: I searched for similar issues in the repository.
options:
- 'Yes'
validations:
required: true

---

.Github/ISSUE TEMPLATE/Config.Yml (.github/ISSUE_TEMPLATE/config.yml)

blank_issues_enabled: false
contact_links:
- name: Question
url: https://github.com/software-mansion/react-native-gesture-handler/discussions/categories/q-a
about: Please ask and answer questions here.

---

.Github/Workflows/Android Basic.Yml (.github/workflows/android-basic.yml)

name: Build Android (basic-example)

on:
pull_request:
paths:
- .github/workflows/android-basic.yml
- .github/workflows/android-build.yml
- packages/react-native-gesture-handler/package.json
- packages/react-native-gesture-handler/android/
- packages/react-native-gesture-handler/shared/
- packages/react-native-gesture-handler/src/specs/
- apps/basic-example/
- '!apps/basic-example/ios/'
- '!apps/basic-example/Gemfile*'
- rnrepo.config.json
- yarn.lock
push:
branches:
- main
workflow_dispatch:

concurrency:
group: android-basic-${{ github.ref }}
cancel-in-progress: true

jobs:
basic-example:
if: github.repository == 'software-mansion/react-native-gesture-handler'

uses: ./.github/workflows/android-build.yml
with:
app: basic-example
artifact-name: android-apk-basic-example
abi: x86_64

---

.Github/Workflows/Android Build.Yml (.github/workflows/android-build.yml)

name: Build Android app

Reusable: builds one example app in Release and uploads the APK.


on:
workflow_call:
inputs:
app:
description: Directory under apps/ holding the example app to build.
required: true
type: string
artifact-name:
description: Name to upload the built APK under.
required: true
type: string
abi:
description: ABI to build. Must match the emulator the e2e job installs the APK on.
required: true
type: string

jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 60

steps:
- name: checkout
uses: actions/checkout@v4

- name: Use Java 17
uses: actions/setup-java@v4
with:
distribution: oracle
java-version: 17

- name: Use Node.js 24
uses: actions/setup-node@v6
with:
node-version: 24
cache: yarn

- name: Install node dependencies
working-directory: apps/${{ inputs.app }}
run: yarn install --immutable

- name: Build app in Release mode
working-directory: apps/${{ inputs.app }}/android
run: ./gradlew :app:assembleRelease --console=plain -PreactNativeArchitectures=${{ inputs.abi }}

- name: Upload APK artifact
uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-name }}
path: apps/${{ inputs.app }}/android/app/build/outputs/apk/release/*.apk
if-no-files-found: error
compression-level: 0
retention-days: 3

---

.Github/Workflows/Android.Yml (.github/workflows/android.yml)

name: Build Android (expo-example)

on:
pull_request:
paths:
- .github/workflows/android.yml
- .github/workflows/android-build.yml
- packages/react-native-gesture-handler/package.json
- packages/react-native-gesture-handler/android/
- packages/react-native-gesture-handler/shared/
- packages/react-native-gesture-handler/src/
- apps/expo-example/
- apps/common-app/
- rnrepo.config.json
- yarn.lock
push:
branches:
- main
workflow_dispatch:

concurrency:
group: android-${{ github.ref }}
cancel-in-progress: true

jobs:
expo-example:
if: github.repository == 'software-mansion/react-native-gesture-handler'

uses: ./.github/workflows/android-build.yml
with:
app: expo-example
artifact-name: android-apk-expo-example
abi: x86_64

---

.Github/Workflows/Close When Stale.Yml (.github/workflows/close-when-stale.yml)

name: Check for stale issues

on:
schedule:
- cron: '37 21 *' # at 21:37 every day
issues:
types: [edited]
issue_comment:
types: [created, edited]
workflow_dispatch:

jobs:
main:
if: github.repository == 'software-mansion/react-native-gesture-handler'
runs-on: ubuntu-latest

steps:
- name: Checkout Actions
uses: actions/checkout@v4
with:
repository: software-mansion-labs/swmansion-bot
ref: stable

- uses: actions/cache@v4
with:
path: '/node_modules'
key: ${{ runner.os }}-modules-${{ hashFiles('/yarn.lock') }}

- name: Install Actions
run: yarn install

- name: Close when stale
uses: ./close-when-stale
with:
close-when-stale-label: Close when stale
days-to-close: 20

---

.Github/Workflows/Docs Check.Yml (.github/workflows/docs-check.yml)

name: Check documentation

on:
pull_request:
paths:
- packages/docs-gesture-handler/
workflow_dispatch:

jobs:
check:
if: github.repository == 'software-mansion/react-native-gesture-handler'

runs-on: ubuntu-latest
env:
WORKING_DIRECTORY: packages/docs-gesture-handler
concurrency:
group: docs-check-${{ github.ref }}
cancel-in-progress: true

steps:
- name: checkout
uses: actions/checkout@v4

- name: Use Node.js 24
uses: actions/setup-node@v6
with:
node-version: 24

- name: Install node dependencies
working-directory: ${{ env.WORKING_DIRECTORY }}
run: yarn

- name: Generate docs
working-directory: ${{ env.WORKING_DIRECTORY }}
run: yarn build

---

.Github/Workflows/Ios Basic.Yml (.github/workflows/ios-basic.yml)

name: Build iOS (basic-example)

on:
pull_request:
paths:
- .github/workflows/ios-basic.yml
- .github/workflows/ios-build.yml
- packages/react-native-gesture-handler/package.json
- packages/react-native-gesture-handler/RNGestureHandler.podspec
- packages/react-native-gesture-handler/apple/
- packages/react-native-gesture-handler/shared/
- packages/react-native-gesture-handler/src/specs/
- apps/basic-example/
- '!apps/basic-example/android/'
- rnrepo.config.json
- yarn.lock
push:
branches:
- main
workflow_dispatch:

concurrency:
group: ios-basic-${{ github.ref }}
cancel-in-progress: true

jobs:
basic-example:
if: github.repository == 'software-mansion/react-native-gesture-handler'

uses: ./.github/workflows/ios-build.yml
with:
app: basic-example
scheme: BasicExample
artifact-name: ios-app-basic-example
xcode-version: '26.4.1'

---

.Github/Workflows/Ios Build.Yml (.github/workflows/ios-build.yml)

name: Build iOS app

Reusable: builds one example app for the simulator in Release and uploads the


.app bundle.


on:
workflow_call:
inputs:
app:
description: Directory under apps/ holding the example app to build.
required: true
type: string
scheme:
description: Xcode scheme of the app (also the workspace name).
required: true
type: string
artifact-name:
description: Name to upload the packaged .app bundle under.
required: true
type: string
xcode-version:
description: Xcode version to build with.
required: true
type: string

jobs:
build:
runs-on: macos-26
timeout-minutes: 60

steps:
- name: checkout
uses: actions/checkout@v4

- name: Select Xcode
env:
XCODE_VERSION: ${{ inputs.xcode-version }}
run: |
XCODE_APP="/Applications/Xcode_${XCODE_VERSION}.app"
if [ ! -d "$XCODE_APP" ]; then
echo "Xcode ${XCODE_VERSION} is not installed on this runner. Available:" >&2
ls -d /Applications/Xcode*.app >&2
exit 1
fi
echo "Using $XCODE_APP"
echo "DEVELOPER_DIR=$XCODE_APP/Contents/Developer" >> "$GITHUB_ENV"
DEVELOPER_DIR="$XCODE_APP/Contents/Developer" xcodebuild -version

- name: Use Node.js 24
uses: actions/setup-node@v6
with:
node-version: 24
cache: yarn

- name: Install node dependencies
working-directory: apps/${{ inputs.app }}
run: yarn install --immutable

# expo-example runs expo prebuild` on postinstall, which installs its pods.
- name: Install pods
if: ${{ inputs.app == 'basic-example' }}
working-directory: apps/${{ inputs.app }}/ios
run: bundle install && NO_FLIPPER=1 bundle exec pod install

- name: Build app in Release mode
working-directory: apps/${{ inputs.app }}/ios
run: |
xcodebuild \
-workspace ${{ inputs.scheme }}.xcworkspace \
-scheme ${{ inputs.scheme }} \
-configuration Release \
-sdk iphonesimulator \
-destination 'generic/platform=iOS Simulator' \
-derivedDataPath build \
CODE_SIGNING_ALLOWED=NO \
build

# Artifact zips drop POSIX permissions and symlinks, which would leave the
# .app with a non-executable binary — tar the bundle to keep it installable.
- name: Package app bundle
working-directory: apps/${{ inputs.app }}/ios
run: |
APP_PATH=$(find build/Build/Products/Release-iphonesimulator -maxdepth 1 -name '*.app' -type d | head -1)
if [ -z "$APP_PATH" ]; then
echo "Could not find built .app" >&2
exit 1
fi
echo "Packaging $APP_PATH"
mkdir -p "$RUNNER_TEMP/artifact"
tar -czf "$RUNNER_TEMP/artifact/app.tar.gz" \
-C "$(dirname "$APP_PATH")" "$(basename "$APP_PATH")"

- name: Upload app artifact
uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-name }}
path: ${{ runner.temp }}/artifact/app.tar.gz
if-no-files-found: error
compression-level: 0
retention-days: 3

---

.Github/Workflows/Ios.Yml (.github/workflows/ios.yml)

name: Build iOS (expo-example)

on:
pull_request:
paths:
- .github/workflows/ios.yml
- .github/workflows/ios-build.yml
- packages/react-native-gesture-handler/package.json
- packages/react-native-gesture-handler/RNGestureHandler.podspec
- packages/react-native-gesture-handler/apple/
- packages/react-native-gesture-handler/shared/
- packages/react-native-gesture-handler/src/
- apps/expo-example/
- apps/common-app/
- rnrepo.config.json
- yarn.lock
push:
branches:
- main
workflow_dispatch:

concurrency:
group: ios-${{ github.ref }}
cancel-in-progress: true

jobs:
expo-example:
if: github.repository == 'software-mansion/react-native-gesture-handler'

uses: ./.github/workflows/ios-build.yml
with:
app: expo-example
scheme: ExpoExample
artifact-name: ios-app-expo-example
xcode-version: '26.4.1'

---

.Github/Workflows/Kotlin Lint.Yml (.github/workflows/kotlin-lint.yml)

name: Kotlin Lint

on:
pull_request:
paths:
- packages/react-native-gesture-handler/android/
push:
branches:
- main
workflow_dispatch:

jobs:
check:
if: github.repository == 'software-mansion/react-native-gesture-handler'

runs-on: ubuntu-latest
env:
WORKING_DIRECTORY: packages/react-native-gesture-handler
concurrency:
group: kotlin-lint-${{ github.ref }}
cancel-in-progress: true

steps:
- name: checkout
uses: actions/checkout@v4

- name: Use Java 17
uses: actions/setup-java@v4
with:
distribution: oracle
java-version: 17

- name: Use Node.js 24
uses: actions/setup-node@v6
with:
node-version: 24
cache: yarn

- uses: actions/cache@v4
with:
path: '/node_modules'
key: ${{ runner.os }}-modules-${{ hashFiles('/yarn.lock') }}

- name: Install node dependencies
run: yarn install --immutable

- name: Restore build from cache
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
${{ env.WORKING_DIRECTORY }}/android/build
${{ env.WORKING_DIRECTORY }}/android/.gradle
key: ${{ runner.os }}-kotlin-lint-gradle-${{ hashFiles('${{ env.WORKING_DIRECTORY }}//.gradle', '${{ env.WORKING_DIRECTORY }}//gradle-wrapper.properties', '${{ env.WORKING_DIRECTORY }}/android/build.gradle') }}

- name: Lint
run: yarn workspace react-native-gesture-handler lint:android

---

.Github/Workflows/Macos Build.Yml (.github/workflows/macos-build.yml)

name: Test macOS build

on:
pull_request:
paths:
- .github/workflows/macos-build.yml
- packages/react-native-gesture-handler/RNGestureHandler.podspec
- packages/react-native-gesture-handler/apple/
- apps/macos-example/macos/
push:
branches:
- main
workflow_dispatch:

jobs:
build:
if: github.repository == 'software-mansion/react-native-gesture-handler'

runs-on: macos-15-xlarge
env:
WORKING_DIRECTORY: apps/macos-example
concurrency:
group: macos-${{ github.ref }}
cancel-in-progress: true

steps:
- name: checkout
uses: actions/checkout@v4

- name: Use Node.js 22
uses: actions/setup-node@v6
with:
node-version: 22
cache: yarn

- name: Install node dependencies
working-directory: ${{ env.WORKING_DIRECTORY }}
run: yarn

- name: Install pods
working-directory: ${{ env.WORKING_DIRECTORY }}/macos
run: bundle install && bundle exec pod install

- name: Build app
working-directory: ${{ env.WORKING_DIRECTORY }}
run: npx react-native-macos run-macos

---

.Github/Workflows/Main.Yml (.github/workflows/main.yml)

name: Publish to GitHub Pages

on:
push:
branches:
- main

jobs:
publish:
if: github.repository == 'software-mansion/react-native-gesture-handler'

runs-on: ubuntu-latest

permissions:
contents: write

steps:
- name: Check out
uses: actions/checkout@v6

- name: Generate docs
run: >-
git config --local user.email "[email protected]"
&& git config --local user.name "GitHub Action"
&& cd packages/docs-gesture-handler
&& yarn
&& yarn build

- name: Publish generated content to GitHub Pages
uses: JamesIves/github-pages-deploy-action@v4
with:
folder: packages/docs-gesture-handler/build
branch: gh-pages
token: ${{ secrets.GITHUB_TOKEN }}

---

.Github/Workflows/Needs More Info.Yml (.github/workflows/needs-more-info.yml)

name: Check issue template

on:
issues:
types: [opened, edited]

jobs:
main:
if: ${{ github.repository == 'software-mansion/react-native-gesture-handler' && !contains(github.event.issue.labels.*.name, 'Maintainer issue') }}

runs-on: ubuntu-latest
concurrency:
group: needs-more-info-${{ github.event.issue.number }}
cancel-in-progress: true

steps:
- name: Checkout Actions
uses: actions/checkout@v4
with:
repository: software-mansion-labs/swmansion-bot
ref: stable

- uses: actions/cache@v4
with:
path: '/node_modules'
key: ${{ runner.os }}-modules-${{ hashFiles('/yarn.lock') }}

- name: Install Actions
run: yarn install

- name: Needs More Info
uses: ./needs-more-info
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
needs-more-info-label: 'Missing info'
required-sections: 'Description;Steps to reproduce;A link to a Gist, an Expo Snack or a link to a repository based on this template that reproduces the bug.;Gesture Handler version;React Native version;Platforms'
needs-more-info-response: "Hey! 👋 \n\nIt looks like you've omitted a few important sections from the issue template."
# This action also appends something like: "Please complete X, Y and Z sections." to the response.
# Code responsible for this can be found here: https://github.com/software-mansion-labs/swmansion-bot/blob/main/needs-more-info/MissingSectionsFormatter.js

---

.Github/Workflows/Needs Repro.Yml (.github/workflows/needs-repro.yml)

name: Check for reproduction

on:
issues:
types: [opened, edited]

jobs:
main:
if: ${{ github.repository == 'software-mansion/react-native-gesture-handler' && !contains(github.event.issue.labels.*.name, 'Maintainer issue') }}

runs-on: ubuntu-latest
concurrency:
group: needs-repro-${{ github.event.issue.number }}
cancel-in-progress: true

steps:
- name: Checkout Actions
uses: actions/checkout@v4
with:
repository: software-mansion-labs/swmansion-bot
ref: stable

- uses: actions/cache@v4
with:
path: '/node_modules'
key: ${{ runner.os }}-modules-${{ hashFiles('/yarn.lock') }}

- name: Install Actions
run: yarn install

- name: Needs Repro
uses: ./needs-repro
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
needs-repro-label: 'Missing repro'
needs-repro-response: "Hey! 👋 \n\nThe issue doesn't seem to contain a minimal reproduction.\n\nCould you provide a snack or a link to a GitHub repository under your username that reproduces the problem?"
repro-provided-label: 'Repro provided'
check-issues-only-created-after: 2022-02-01

---

.Github/Workflows/Platforms.Yml (.github/workflows/platforms.yml)

name: Check for platforms

on:
issues:
types: [opened, edited]

jobs:
main:
if: ${{ github.repository == 'software-mansion/react-native-gesture-handler' && !contains(github.event.issue.labels.*.name, 'Maintainer issue') }}

runs-on: ubuntu-latest
concurrency:
group: platforms-${{ github.event.issue.number }}
cancel-in-progress: true

steps:
- name: Checkout Actions
uses: actions/checkout@v4
with:
repository: software-mansion-labs/swmansion-bot
ref: stable

- uses: actions/cache@v4
with:
path: '/node_modules'
key: ${{ runner.os }}-modules-${{ hashFiles('/yarn.lock') }}

- name: Install Actions
run: yarn install

- name: Platforms
uses: ./platforms
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
platforms-section-header: Platforms
platforms-comma-separated: true
platforms-with-labels: '{"Android": "Platform: Android", "iOS": "Platform: iOS", "Web": "Platform: Web", "MacOS": "Platform: MacOS"}'

---

.Github/Workflows/Publish Release.Yml (.github/workflows/publish-release.yml)

name: Publish release to npm
on:
# For nightly releases
schedule:
- cron: '27 23 *' # at 23:27 every day
# For manual releases
workflow_dispatch:
inputs:
release-type:
description: Type of release to publish.
type: choice
options:
- stable
- nightly
- beta
- rc
default: stable
version:
description: Specific version to publish (usually inferred from x.y-stable branch name).
type: string
required: false
default: ''
dry-run:
description: Whether to perform a dry run of the publish.
type: boolean
default: true

jobs:
npm-build:
if: github.repository == 'software-mansion/react-native-gesture-handler'
runs-on: ubuntu-latest

permissions:
contents: write
id-token: write # for OIDC

concurrency:
group: publish-${{ github.ref }}
cancel-in-progress: false

steps:
- name: Check out
uses: actions/checkout@v4

- name: Set up environment
shell: bash
run: |
echo "YARN_ENABLE_HARDENED_MODE=0" >> $GITHUB_ENV

- name: Setup Node
uses: actions/setup-node@v6
with:
node-version: 24
cache: 'yarn'
registry-url: https://registry.npmjs.org/

- name: Publish manual release
if: ${{ github.event_name == 'workflow_dispatch' }}
uses: software-mansion/npm-package-publish@273bbdd5df5d28ae2de6c3ecd4a6f8067e4ff370
with:
package-name: 'react-native-gesture-handler'
package-json-path: 'packages/react-native-gesture-handler/package.json'
install-dependencies-command: 'yarn install --immutable'
release-type: ${{ inputs.release-type }}
version: ${{ inputs.version }}
dry-run: ${{ inputs.dry-run }}

- name: Publish automatic nightly release
if: ${{ github.event_name == 'schedule' }}
uses: software-mansion/npm-package-publish@273bbdd5df5d28ae2de6c3ecd4a6f8067e4ff370
with:
package-name: 'react-native-gesture-handler'
package-json-path: 'packages/react-native-gesture-handler/package.json'
install-dependencies-command: 'yarn install --immutable'
release-type: 'nightly'
dry-run: false

---

.Github/Workflows/Rngh Api V3.Yml (.github/workflows/rngh-api-v3.yml)

name: Test Gesture Handler 3 API

on:
pull_request:
paths:
- packages/react-native-gesture-handler/src/v3/
- packages/react-native-gesture-handler/src/__tests__/RelationsTraversal.test.tsx
- packages/react-native-gesture-handler/src/__tests__/API_V3.test.tsx
push:
branches:
- main
workflow_dispatch:

jobs:
check:
if: github.repository == 'software-mansion/react-native-gesture-handler'

runs-on: ubuntu-latest
concurrency:
group: rngh-api-v3-${{ github.ref }}
cancel-in-progress: true

steps:
- name: checkout
uses: actions/checkout@v4

- name: Use Node.js 24
uses: actions/setup-node@v6
with:
node-version: 24
cache: yarn

- name: Install node dependencies
run: yarn --immutable

- name: Run tests
working-directory: packages/react-native-gesture-handler
run: yarn test RelationsTraversal API_V3

---

.Github/Workflows/Run Jest Tests.Yml (.github/workflows/run-jest-tests.yml)

name: Run Jest tests

on:
pull_request:
paths:
- '/*.js'
- '/*.jsx'
- '/*.ts'
- '/*.tsx'
push:
branches:
- main
workflow_dispatch:

jobs:
build:
if: github.repository == 'software-mansion/react-native-gesture-handler'

runs-on: ubuntu-latest
concurrency:
group: jest-${{ github.ref }}
cancel-in-progress: true

steps:
- name: checkout
uses: actions/checkout@v4

- name: Use Node.js 24
uses: actions/setup-node@v6
with:
node-version: 24
cache: yarn

- name: Install node dependencies
run: yarn --immutable

- name: Run jest package tests
run: yarn workspace react-native-gesture-handler test

---

.Github/Workflows/Static Example Apps Checks.Yml (.github/workflows/static-example-apps-checks.yml)

name: Test TypeScript and Lint

on:
pull_request:
paths:
- apps/basic-example/
- apps/common-app/
push:
branches:
- main
workflow_dispatch:

jobs:
check:
if: github.repository == 'software-mansion/react-native-gesture-handler'

runs-on: ubuntu-latest
strategy:
matrix:
working-directory: [apps/basic-example, apps/common-app]
concurrency:
group: typescript-${{ matrix.working-directory }}-${{ github.ref }}
cancel-in-progress: true

steps:
- name: checkout
uses: actions/checkout@v4

- name: Use Node.js 24
uses: actions/setup-node@v6
with:
node-version: 24
cache: yarn

- name: Install node dependencies
run: yarn --immutable

- name: Check types
working-directory: ${{ matrix.working-directory }}
run: yarn ts-check

- name: Lint
working-directory: ${{ matrix.working-directory }}
run: yarn lint-js

---

.Github/Workflows/Static Root Checks.Yml (.github/workflows/static-root-checks.yml)

name: Test TypeScript and Lint

on:
pull_request:
paths:
- packages/react-native-gesture-handler/src/
- packages/react-native-gesture-handler/*
push:
branches:
- main
workflow_dispatch:

jobs:
check:
if: github.repository == 'software-mansion/react-native-gesture-handler'

runs-on: ubuntu-latest
concurrency:
group: static-root-${{ github.ref }}
cancel-in-progress: true

steps:
- name: checkout
uses: actions/checkout@v4

- name: Use Node.js 24
uses: actions/setup-node@v6
with:
node-version: 24
cache: yarn

- name: Install node dependencies
run: yarn --immutable

- name: Check types
run: yarn workspace react-native-gesture-handler ts-check

- name: Lint
run: yarn workspace react-native-gesture-handler lint-js

- name: Check for circular dependencies
run: yarn workspace react-native-gesture-handler circular-dependency-check

---

.Github/Workflows/Yarn Validation.Yml (.github/workflows/yarn-validation.yml)

name: Yarn validation

on:
pull_request:
paths:
- .github/workflows/yarn-validation.yml
- '/package.json'
- '/yarn.lock'
merge_group:
branches:
- main
push:
branches:
- main
paths:
- .github/workflows/yarn-validation.yml
- '/package.json'
- '/yarn.lock'
workflow_call:
workflow_dispatch:

jobs:
check:
if: github.repository == 'software-mansion/react-native-gesture-handler'

runs-on: ubuntu-latest
env:
YARN_ENABLE_HARDENED_MODE: 1

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Use Node.js
uses: actions/setup-node@v6

- name: Install root dependencies
run: yarn install

- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v46
with:
files: |
/package.json
/yarn.lock
files_ignore: |
packages/react-native-gesture-handler/ReanimatedDrawerLayout/package.json
packages/react-native-gesture-handler/ReanimatedSwipeable/package.json
packages/react-native-gesture-handler/jest-utils/package.json

- name: Get list of changed directories
id: changed-dirs
run: |
ECHOLIST=()
for CHANGE in ${{ steps.changed-files.outputs.all_changed_files }}
do
DIR=$(dirname "$CHANGE")
ECHOLIST+=("$DIR")
done
ECHOLIST=($(echo "${ECHOLIST[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' '))
echo "dirList=${ECHOLIST[*]}" >> $GITHUB_OUTPUT

- name: Perform yarn install in changed directories
working-directory: ${{ github.workspace }}
run: |
for DIR in ${{ steps.changed-dirs.outputs.dirList }}
do
echo "Yarn install in $DIR"
echo $(pwd)
cd "$DIR" && yarn install --immutable && cd ${{ github.workspace }}
done

---