Declarative API exposing platform native touch and gesture system to React Native
—
Sophisticated layout and interaction components for complex UI patterns like swipe-to-reveal and drawer layouts.
Component for creating swipe-to-reveal interfaces, commonly used in messaging apps and todo lists for contextual actions.
/**
* Swipeable component for creating swipe-to-reveal interfaces
* Provides left and right swipe actions with customizable animations
*/
function Swipeable(props: {
friction?: number;
leftThreshold?: number;
rightThreshold?: number;
dragOffsetFromLeftEdge?: number;
dragOffsetFromRightEdge?: number;
overshootLeft?: boolean;
overshootRight?: boolean;
overshootFriction?: number;
onSwipeableLeftOpen?: () => void;
onSwipeableRightOpen?: () => void;
onSwipeableOpen?: (direction: "left" | "right") => void;
onSwipeableClose?: (direction: "left" | "right") => void;
onSwipeableLeftWillOpen?: () => void;
onSwipeableRightWillOpen?: () => void;
onSwipeableWillOpen?: (direction: "left" | "right") => void;
onSwipeableWillClose?: (direction: "left" | "right") => void;
renderLeftActions?: (
progressAnimatedValue: Animated.AnimatedAddition,
dragX: Animated.AnimatedAddition
) => React.ReactNode;
renderRightActions?: (
progressAnimatedValue: Animated.AnimatedAddition,
dragX: Animated.AnimatedAddition
) => React.ReactNode;
useNativeAnimations?: boolean;
animationOptions?: object;
containerStyle?: StyleProp<ViewStyle>;
childrenContainerStyle?: StyleProp<ViewStyle>;
enableTrackpadTwoFingerGesture?: boolean;
children: React.ReactNode;
}): JSX.Element;
interface SwipeableProps {
friction?: number;
leftThreshold?: number;
rightThreshold?: number;
dragOffsetFromLeftEdge?: number;
dragOffsetFromRightEdge?: number;
overshootLeft?: boolean;
overshootRight?: boolean;
overshootFriction?: number;
onSwipeableLeftOpen?: () => void;
onSwipeableRightOpen?: () => void;
onSwipeableOpen?: (direction: "left" | "right") => void;
onSwipeableClose?: (direction: "left" | "right") => void;
onSwipeableLeftWillOpen?: () => void;
onSwipeableRightWillOpen?: () => void;
onSwipeableWillOpen?: (direction: "left" | "right") => void;
onSwipeableWillClose?: (direction: "left" | "right") => void;
renderLeftActions?: (
progressAnimatedValue: Animated.AnimatedAddition,
dragX: Animated.AnimatedAddition
) => React.ReactNode;
renderRightActions?: (
progressAnimatedValue: Animated.AnimatedAddition,
dragX: Animated.AnimatedAddition
) => React.ReactNode;
useNativeAnimations?: boolean;
animationOptions?: object;
containerStyle?: StyleProp<ViewStyle>;
childrenContainerStyle?: StyleProp<ViewStyle>;
enableTrackpadTwoFingerGesture?: boolean;
children: React.ReactNode;
}Usage Example:
import React, { useRef } from "react";
import { View, Text, Animated, TouchableOpacity } from "react-native";
import { Swipeable } from "react-native-gesture-handler";
function SwipeableItem({ item, onDelete, onArchive }) {
const swipeableRef = useRef<Swipeable>(null);
const renderLeftActions = (progress, dragX) => {
const trans = dragX.interpolate({
inputRange: [0, 50, 100, 101],
outputRange: [-20, 0, 0, 1],
extrapolate: "clamp",
});
return (
<View style={{ flex: 1, backgroundColor: "green", justifyContent: "center" }}>
<Animated.View style={{ transform: [{ translateX: trans }] }}>
<TouchableOpacity
onPress={() => {
onArchive(item.id);
swipeableRef.current?.close();
}}
style={{ paddingHorizontal: 20 }}
>
<Text style={{ color: "white", fontWeight: "bold" }}>Archive</Text>
</TouchableOpacity>
</Animated.View>
</View>
);
};
const renderRightActions = (progress, dragX) => {
const trans = dragX.interpolate({
inputRange: [-101, -100, -50, 0],
outputRange: [1, 0, 0, 20],
extrapolate: "clamp",
});
return (
<View style={{ flex: 1, backgroundColor: "red", justifyContent: "center" }}>
<Animated.View style={{ transform: [{ translateX: trans }] }}>
<TouchableOpacity
onPress={() => {
onDelete(item.id);
swipeableRef.current?.close();
}}
style={{ paddingHorizontal: 20 }}
>
<Text style={{ color: "white", fontWeight: "bold" }}>Delete</Text>
</TouchableOpacity>
</Animated.View>
</View>
);
};
return (
<Swipeable
ref={swipeableRef}
renderLeftActions={renderLeftActions}
renderRightActions={renderRightActions}
leftThreshold={80}
rightThreshold={80}
friction={1.5}
>
<View style={{
padding: 15,
backgroundColor: "white",
borderBottomWidth: 1,
borderBottomColor: "#eee"
}}>
<Text>{item.title}</Text>
</View>
</Swipeable>
);
}Modern pressable component using the new gesture API, providing advanced press handling with gesture coordination.
/**
* Modern pressable component using the new gesture API
* Provides sophisticated press detection with gesture relationship management
*/
function Pressable(props: {
children?: React.ReactNode | ((state: PressableStateCallbackType) => React.ReactNode);
onPress?: (event?: GestureResponderEvent) => void;
onPressIn?: (event?: GestureResponderEvent) => void;
onPressOut?: (event?: GestureResponderEvent) => void;
onLongPress?: (event?: GestureResponderEvent) => void;
onHoverIn?: (event?: MouseEvent) => void;
onHoverOut?: (event?: MouseEvent) => void;
disabled?: boolean;
delayLongPress?: number;
hitSlop?: Insets;
pressRetentionOffset?: Insets;
android_disableSound?: boolean;
android_ripple?: AndroidRippleConfig;
testID?: string;
style?: StyleProp<ViewStyle> | ((state: PressableStateCallbackType) => StyleProp<ViewStyle>);
unstable_pressDelay?: number;
// Gesture Handler specific props
simultaneousWithExternalGesture?: GestureRef[];
requireExternalGestureToFail?: GestureRef[];
blocksExternalGesture?: GestureRef[];
}): JSX.Element;
interface PressableProps {
children?: React.ReactNode | ((state: PressableStateCallbackType) => React.ReactNode);
onPress?: (event?: GestureResponderEvent) => void;
onPressIn?: (event?: GestureResponderEvent) => void;
onPressOut?: (event?: GestureResponderEvent) => void;
onLongPress?: (event?: GestureResponderEvent) => void;
onHoverIn?: (event?: MouseEvent) => void;
onHoverOut?: (event?: MouseEvent) => void;
disabled?: boolean;
delayLongPress?: number;
hitSlop?: Insets;
pressRetentionOffset?: Insets;
android_disableSound?: boolean;
android_ripple?: AndroidRippleConfig;
testID?: string;
style?: StyleProp<ViewStyle> | ((state: PressableStateCallbackType) => StyleProp<ViewStyle>);
unstable_pressDelay?: number;
simultaneousWithExternalGesture?: GestureRef[];
requireExternalGestureToFail?: GestureRef[];
blocksExternalGesture?: GestureRef[];
}
interface PressableStateCallbackType {
pressed: boolean;
hovered?: boolean;
focused?: boolean;
}
interface AndroidRippleConfig {
color?: string;
borderless?: boolean;
radius?: number;
foreground?: boolean;
}Usage Example:
import React from "react";
import { Text, View } from "react-native";
import { Pressable } from "react-native-gesture-handler";
function MyPressable() {
return (
<Pressable
onPress={() => console.log("Pressable pressed")}
onLongPress={() => console.log("Pressable long pressed")}
android_ripple={{ color: "rgba(0,0,0,0.1)", borderless: false }}
style={({ pressed }) => [
{
padding: 15,
backgroundColor: pressed ? "#e0e0e0" : "white",
borderRadius: 8,
borderWidth: 1,
borderColor: "#ccc",
},
]}
>
{({ pressed }) => (
<Text style={{ color: pressed ? "blue" : "black" }}>
{pressed ? "Pressed!" : "Press me"}
</Text>
)}
</Pressable>
);
}Drawer layout component for creating side navigation panels. This component is deprecated in favor of the react-native-reanimated version.
/**
* Drawer layout component (deprecated)
* Use react-native-reanimated drawer implementations instead
* @deprecated Use DrawerLayout from react-native-reanimated
*/
function DrawerLayout(props: {
drawerPosition: DrawerPosition;
drawerWidth?: number;
drawerBackgroundColor?: string;
drawerLockMode?: DrawerLockMode;
keyboardDismissMode?: DrawerKeyboardDismissMode;
onDrawerClose?: () => void;
onDrawerOpen?: () => void;
onDrawerSlide?: (event: DrawerSlideEvent) => void;
onDrawerStateChanged?: (newState: DrawerState) => void;
renderNavigationView: () => React.ReactNode;
statusBarBackgroundColor?: string;
drawerType?: DrawerType;
overlayColor?: string;
contentContainerStyle?: StyleProp<ViewStyle>;
edgeWidth?: number;
minSwipeDistance?: number;
hideStatusBar?: boolean;
statusBarAnimation?: "slide" | "fade" | "none";
children?: React.ReactNode;
}): JSX.Element;
interface DrawerLayoutProps {
drawerPosition: DrawerPosition;
drawerWidth?: number;
drawerBackgroundColor?: string;
drawerLockMode?: DrawerLockMode;
keyboardDismissMode?: DrawerKeyboardDismissMode;
onDrawerClose?: () => void;
onDrawerOpen?: () => void;
onDrawerSlide?: (event: DrawerSlideEvent) => void;
onDrawerStateChanged?: (newState: DrawerState) => void;
renderNavigationView: () => React.ReactNode;
statusBarBackgroundColor?: string;
drawerType?: DrawerType;
overlayColor?: string;
contentContainerStyle?: StyleProp<ViewStyle>;
edgeWidth?: number;
minSwipeDistance?: number;
hideStatusBar?: boolean;
statusBarAnimation?: "slide" | "fade" | "none";
children?: React.ReactNode;
}
// Drawer-related types
type DrawerPosition = "left" | "right";
type DrawerState = "Idle" | "Dragging" | "Settling";
type DrawerType = "front" | "back" | "slide";
type DrawerLockMode = "unlocked" | "locked-closed" | "locked-open";
type DrawerKeyboardDismissMode = "none" | "on-drag";
interface DrawerSlideEvent {
nativeEvent: {
offset: number;
};
}Function for wrapping React Native components to add gesture handler capabilities.
/**
* Creates a native wrapper for React Native components
* Adds gesture handling capabilities to existing components
*/
function createNativeWrapper<T>(
Component: React.ComponentType<T>,
config?: {
shouldCancelWhenOutside?: boolean;
shouldActivateOnStart?: boolean;
disallowInterruption?: boolean;
}
): React.ComponentType<T & {
waitFor?: React.Ref<any> | React.Ref<any>[];
simultaneousHandlers?: React.Ref<any> | React.Ref<any>[];
}>;Usage Example:
import React from "react";
import { View } from "react-native";
import { createNativeWrapper } from "react-native-gesture-handler";
const WrappedView = createNativeWrapper(View, {
shouldCancelWhenOutside: true,
shouldActivateOnStart: true,
});
function MyWrappedView() {
return (
<WrappedView
style={{ width: 100, height: 100, backgroundColor: "red" }}
onGestureEvent={(event) => {
console.log("Gesture event:", event.nativeEvent);
}}
/>
);
}Advanced components work seamlessly with react-native-reanimated for smooth animations:
import React from "react";
import { View } from "react-native";
import Animated, {
useSharedValue,
useAnimatedStyle,
withSpring
} from "react-native-reanimated";
import { Pressable } from "react-native-gesture-handler";
function AnimatedPressable() {
const scale = useSharedValue(1);
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ scale: scale.value }],
}));
return (
<Pressable
onPressIn={() => {
scale.value = withSpring(0.95);
}}
onPressOut={() => {
scale.value = withSpring(1);
}}
>
<Animated.View style={[{ padding: 20, backgroundColor: "blue" }, animatedStyle]}>
{/* Content */}
</Animated.View>
</Pressable>
);
}Customize Swipeable animations with detailed control:
import React from "react";
import { View, Text, Animated } from "react-native";
import { Swipeable } from "react-native-gesture-handler";
function CustomAnimatedSwipeable() {
const renderRightActions = (progress, dragX) => {
// Custom scaling animation
const scale = progress.interpolate({
inputRange: [0, 1],
outputRange: [0.8, 1],
});
// Custom opacity animation
const opacity = progress.interpolate({
inputRange: [0, 0.5, 1],
outputRange: [0, 0.5, 1],
});
return (
<Animated.View
style={{
flex: 1,
backgroundColor: "red",
justifyContent: "center",
alignItems: "center",
transform: [{ scale }],
opacity,
}}
>
<Text style={{ color: "white", fontWeight: "bold" }}>Delete</Text>
</Animated.View>
);
};
return (
<Swipeable
renderRightActions={renderRightActions}
friction={1.5}
rightThreshold={40}
>
<View style={{ padding: 20, backgroundColor: "white" }}>
<Text>Swipe left for custom animation</Text>
</View>
</Swipeable>
);
}Advanced components are optimized for memory efficiency:
Components leverage native implementations for optimal performance:
// ✅ Good: Use refs for programmatic control
const swipeableRef = useRef<Swipeable>(null);
// ✅ Good: Cleanup in useEffect
useEffect(() => {
return () => {
swipeableRef.current?.close();
};
}, []);
// ❌ Avoid: Creating new objects in render
const renderActions = () => ({ flex: 1 }); // Creates new object each render
// ✅ Better: Use static styles or useMemo
const actionStyles = { flex: 1 };
const renderActions = () => actionStyles;Install with Tessl CLI
npx tessl i tessl/npm-react-native-gesture-handler