Project React Native Reanimated Modal
Expand description
A lightweight, scalable, flexible, and high-performance modal component. Based on the vanilla Modal component for maximum compatibility and native feel. Built with react-native-reanimated and react-native-gesture-handler.

✨ Features
- 🚀 Performance: Built with react-native-reanimated for 60fps animations that run on the UI thread
- 🎨 Smooth Animations: Supports fade, slide, and scale animations with customizable configs
- 👆 Gesture Support: Interactive swipe-to-dismiss in any direction (up, down, left, right)
- 🪶 Lightweight: Minimal dependencies and smaller bundle size compared to alternatives
- 📱 Native Feel: Uses React Native's Modal component as foundation for platform consistency
- 🔧 Flexible: Highly customizable with extensive prop options
- 📚 TypeScript: Full TypeScript support out of the box
- 🔄 Multi-Modal: Easy integration with React Navigation and support for multiple overlays
🎮 Example
- Install Expo Go on your phone
- Scan the QR code with your camera
- Open the link in Expo Go
- Explore example app!
Or browse the code: 📂 View Example Code →
📚 Documentation
Full API and usage documentation: 🗂️ View Documentation →
📦 Installation
npm install react-native-reanimated-modal
yarn add react-native-reanimated-modal
pnpm add react-native-reanimated-modal
bun add react-native-reanimated-modal
Required Dependencies
This library depends on the following peer dependencies:
- react-native-reanimated (>= 3.0.0)
- react-native-gesture-handler (>= 2.0.0)
- react-native-worklets (>= 0.5.0) - Required for Reanimated 4.0.0+
Note: Make sure to follow the installation guides for all libraries, as they require additional platform-specific setup steps.
🚀 Basic Usage
import React, { useState } from 'react';
import { View, Text, Button } from 'react-native';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { Modal } from 'react-native-reanimated-modal';
const App = () => {
const [visible, setVisible] = useState(false);
return (
<GestureHandlerRootView>
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Button title="Show Modal" onPress={() => setVisible(true)} />
<Modal visible={visible} onHide={() => setVisible(false)}>
<View style={{
backgroundColor: 'white',
padding: 20,
borderRadius: 10,
margin: 20,
}}>
<Text>Hello from Modal!</Text>
<Button title="Close" onPress={() => setVisible(false)} />
</View>
</Modal>
</View>
</GestureHandlerRootView>
);
};
📖 API Documentation
New Configuration-Based API (Recommended)
Starting from v1.1.0, we recommend using the new configuration-based API for better type safety and cleaner code:
Animation Configurations
import type { ModalAnimationConfig } from 'react-native-reanimated-modal';
// Scale animation with custom settings
const scaleConfig: ModalAnimationConfig<'scale'> = {
type: 'scale',
duration: 400,
scaleFactor: 0.8, // Start from 80% size
};
// Fade animation
const fadeConfig: ModalAnimationConfig<'fade'> = {
type: 'fade',
duration: 300,
};
// Slide animation with complex directions
const slideConfig: ModalAnimationConfig<'slide'> = {
type: 'slide',
duration: 500,
direction: {
start: 'down', // Slides in from bottom
end: ['down', 'right'], // Can dismiss by swiping down or right
},
};
// Simple slide animation
const simpleSlideConfig: ModalAnimationConfig<'slide'> = {
type: 'slide',
duration: 400,
direction: 'up', // Both slide-in and dismiss direction
};
Swipe Configurations
import type { ModalSwipeConfig } from 'react-native-reanimated-modal';
// Basic swipe config
const basicSwipe: ModalSwipeConfig = {
enabled: true,
directions: ['down', 'left', 'right'], // Allow swiping in these directions
threshold: 120,
};
// Advanced swipe config with custom bounce
const advancedSwipe: ModalSwipeConfig = {
enabled: true,
directions: ['up', 'down'], // Only vertical swipes
threshold: 80,
bounceSpringConfig: {
dampingRatio: 0.7,
duration: 400,
},
bounceOpacityThreshold: 0.1,
};
// Disabled swipe
const noSwipe: ModalSwipeConfig = {
enabled: false,
};
Usage Examples
<Modal
visible={visible}
animation={scaleConfig}
swipe={advancedSwipe}
>
{/* Your content */}
</Modal>
// Or with inline configs
<Modal
visible={visible}
animation={{
type: 'scale',
duration: 600,
scaleFactor: 0.9,
}}
swipe={{
enabled: true,
threshold: 100,
}}
>
{/* Your content */}
</Modal>
// Backdrop examples
<Modal
visible={visible}
onBackdropPress={false} // Prevent backdrop from closing modal
onHide={() => setVisible(false)} // Only programmatic close allowed
>
{/* Your content */}
</Modal>
<Modal
visible={visible}
backdrop={{ color: 'red', opacity: 0.8 }} // Custom backdrop styling
onBackdropPress={() => console.log('Backdrop pressed!')} // Custom handler
>
{/* Your content */}
</Modal>
// Legacy string syntax still supported
<Modal
visible={visible}
animation="fade" // Equivalent to { type: 'fade', duration: 300 }
>
{/* Your content */}
</Modal>
Test IDs
You can pass custom testID props to key elements for easier testing:
Prop | Type | Default | Description |
---|---|---|---|
backdropTestID |
string |
'modal-backdrop' |
testID for the backdrop Pressable |
contentTestID |
string |
'modal-content' |
testID for the modal content (Animated.View) |
containerTestID |
string |
'modal-container' |
testID for the root container View |
These props are optional and help you write robust e2e/unit tests.
Props
Prop | Type | Default | Description |
---|---|---|---|
visible |
boolean |
false |
Controls the visibility of the modal |
closable |
boolean |
true |
Whether the modal can be closed by user actions |
children |
ReactNode |
- | Content to render inside the modal |
style |
StyleProp<ViewStyle> |
- | Style for the modal container |
contentContainerStyle |
StyleProp<ViewStyle> |
- | Style for the content wrapper |
Configuration Props
Prop | Type | Default | Description |
---|---|---|---|
animation |
ModalAnimationConfigUnion | ModalAnimation |
{ type: 'fade', duration: 300 } |
Animation configuration object or simple animation type string |
swipe |
ModalSwipeConfig | false |
{ enabled: true, directions: ['down'], threshold: 100 } |
Swipe gesture configuration |
Backdrop Props
Prop | Type | Default | Description |
---|---|---|---|
backdrop |
ModalBackdropConfig | ReactNode | false |
{ enabled: true, color: 'black', opacity: 0.7 } |
Backdrop configuration: false (no backdrop), ReactNode for custom backdrop, or config object |
onBackdropPress |
(() => void) | false |
- | Callback when backdrop is pressed. Set to false to prevent backdrop from closing the modal |
Other Props
Prop | Type | Default | Description |
---|---|---|---|
coverScreen |
boolean |
false |
If true, covers entire screen without using native Modal |
Event Props
Prop | Type | Description |
---|---|---|
onShow |
() => void |
Called when modal appears |
onHide |
() => void |
Called when modal disappears |
React Native Modal Props
The component also accepts these props from React Native's Modal:
hardwareAccelerated
(Android)navigationBarTranslucent
(Android)statusBarTranslucent
(Android)onOrientationChange
(iOS)supportedOrientations
(iOS)
Constants
The library exports several useful constants for customization:
import {
DEFAULT_MODAL_ANIMATION_DURATION, // 300
DEFAULT_MODAL_SCALE_FACTOR, // 0.8
DEFAULT_MODAL_BACKDROP_CONFIG, // { enabled: true, color: 'black', opacity: 0.7 }
DEFAULT_MODAL_SWIPE_THRESHOLD, // 100
DEFAULT_MODAL_BOUNCE_SPRING_CONFIG, // { dampingRatio: 0.5, duration: 700 }
DEFAULT_MODAL_BOUNCE_OPACITY_THRESHOLD, // 0.05
DEFAULT_MODAL_SWIPE_DIRECTION, // 'down'
} from 'react-native-reanimated-modal';
// Use in your custom configurations
const customAnimationConfig = {
type: 'scale',
duration: DEFAULT_MODAL_ANIMATION_DURATION * 2, // 600ms
scaleFactor: DEFAULT_MODAL_SCALE_FACTOR, // 0.8
};
const customBackdropConfig = {
...DEFAULT_MODAL_BACKDROP_CONFIG,
color: 'rgba(0, 0, 0, 0.8)', // Darker backdrop
opacity: 0.9,
};
Types
type SwipeDirection = 'up' | 'down' | 'left' | 'right';
type ModalAnimation = 'fade' | 'slide' | 'scale';
// New Configuration Types
type ModalAnimationConfig<T extends ModalAnimation> =
T extends 'fade' ? FadeAnimationConfig :
T extends 'slide' ? SlideAnimationConfig :
T extends 'scale' ? ScaleAnimationConfig : never;
interface FadeAnimationConfig {
type: 'fade';
duration?: number;
}
interface SlideAnimationConfig {
type: 'slide';
duration?: number;
direction?: SwipeDirection | {
start: SwipeDirection;
end: SwipeDirection | SwipeDirection[]; // swipe enabled for this directions (low priority)
};
}
interface ScaleAnimationConfig {
type: 'scale';
duration?: number;
scaleFactor?: number; // 0-1, default: 0.8
}
interface ModalSwipeConfig {
enabled?: boolean;
directions?: SwipeDirection[], // swipe enabled for this directions (high priority)
threshold?: number;
bounceSpringConfig?: SpringConfig;
bounceOpacityThreshold?: number;
}
interface ModalBackdropConfig {
enabled?: boolean;
color?: string;
opacity?: number;
}
type ModalAnimationConfigUnion =
| FadeAnimationConfig
| SlideAnimationConfig
| ScaleAnimationConfig;
🔄 React Navigation support
When using multiple modals simultaneously with @react-navigation/native-stack
, you can leverage iOS's FullWindowOverlay
for better layering:
import React from 'react';
import { Platform } from 'react-native';
import { FullWindowOverlay } from 'react-native-screens';
import { Modal } from 'react-native-reanimated-modal';
const isIOS = Platform.OS === 'ios';
const withOverlay = (element: React.ReactNode) =>
isIOS ? <FullWindowOverlay>{element}</FullWindowOverlay> : element;
const MultiModalExample = () => {
const [firstModalVisible, setFirstModalVisible] = useState(false);
const [secondModalVisible, setSecondModalVisible] = useState(false);
return withOverlay(
<>
<Modal
visible={firstModalVisible}
coverScreen // Important: excludes native Modal usage
onHide={() => setFirstModalVisible(false)}
>
{/* First modal content */}
</Modal>
<Modal
visible={secondModalVisible}
coverScreen // Important: excludes native Modal usage
onHide={() => setSecondModalVisible(false)}
>
{/* Second modal content */}
</Modal>
</>
);
};
Important: When using multiple modals with
FullWindowOverlay
, always setcoverScreen={true}
prop to exclude the usage of React Native's native Modal component and ensure proper layering.
🎨 Advanced Examples
Fade Animation with Custom Duration
<Modal
visible={visible}
animation={{
type: 'fade',
duration: 400,
}}
swipe={{
directions: ['down', 'right'],
threshold: 100,
}}
onHide={() => setVisible(false)}
>
{/* Modal content */}
</Modal>
Scale Animation with Custom Duration
<Modal
visible={visible}
animation={{
type: 'scale',
duration: 400,
scaleFactor: 0.8,
}}
swipe={{
directions: ['down', 'right'],
threshold: 100,
}}
onHide={() => setVisible(false)}
>
{/* Modal content */}
</Modal>
Custom Slide Animation with Swipe Directions
<Modal
visible={visible}
animation={{
type: 'slide',
duration: 500,
direction: {
start: 'down', // Slides in from bottom
end: ['down', 'right'], // Can dismiss by swiping down or right
},
}}
swipe={{
threshold: 150,
bounceSpringConfig: {
dampingRatio: 0.8,
duration: 400,
},
}}
onHide={() => setVisible(false)}
>
{/* Modal content */}
</Modal>
Note: When using slide animation with complex directions, the
start
property determines the initial slide-in direction, while theend
property (array or single direction) defines the available swipe-to-dismiss directions.
Full Screen Modal
<Modal
visible={visible}
contentContainerStyle={{ flex: 1 }}
animation={{
type: 'slide',
duration: 300,
direction: 'down',
}}
swipe={{
directions: ['down'],
threshold: 80,
}}
backdrop={false} // No backdrop for full screen
onHide={() => setVisible(false)}
>
{/* Modal content */}
</Modal>
Custom Backdrop Examples
// Disable backdrop completely
<Modal visible={visible} backdrop={false}>
{/* Modal content */}
</Modal>
// Custom backdrop configuration
<Modal
visible={visible}
backdrop={{
enabled: true,
color: 'rgba(255, 0, 0, 0.3)',
opacity: 0.8
}}
>
{/* Modal content */}
</Modal>
// Custom backdrop renderer (e.g., BlurView)
<Modal
visible={visible}
backdrop={
<BlurView
style={StyleSheet.absoluteFill}
blurType="light"
blurAmount={10}
/>
}
>
{/* Modal content */}
</Modal>
🤝 Contributing
See the contributing guide to learn how to contribute to the repository and the development workflow.
📄 License
MIT
Made with create-react-native-library
Type Aliases§
Source§type Modal Animation Config<T> = T extends "fade"
? FadeAnimationConfig
: T extends "slide"
? SlideAnimationConfig
: T extends "scale" ? ScaleAnimationConfig : never
type Modal Animation Config<T> = T extends "fade"
? FadeAnimationConfig
: T extends "slide"
? SlideAnimationConfig
: T extends "scale" ? ScaleAnimationConfig : never
Source§type Modal Animation Config Union = FadeAnimationConfig | SlideAnimationConfig | ScaleAnimationConfig
type Modal Animation Config Union = FadeAnimationConfig | SlideAnimationConfig | ScaleAnimationConfig
Union of all animation configuration types.
Variables§
Source§const DEFAULT _ MODAL _ ANIMATION _ CONFIGS: {
fade: FadeAnimationConfig;
scale: ScaleAnimationConfig;
slide: SlideAnimationConfig;
} = ...
const DEFAULT _ MODAL _ ANIMATION _ CONFIGS: {
fade: FadeAnimationConfig;
scale: ScaleAnimationConfig;
slide: SlideAnimationConfig;
} = ...
Default animation configurations.
Source§const DEFAULT _ MODAL _ ANIMATION _ DURATION: 300 = [object Object]
const DEFAULT _ MODAL _ ANIMATION _ DURATION: 300 = [object Object]
Default values and configurations.
Source§const DEFAULT _ MODAL _ BACKDROP _ CONFIG: ModalBackdropConfig = ...
const DEFAULT _ MODAL _ BACKDROP _ CONFIG: ModalBackdropConfig = ...
Default backdrop configuration.
Source§const DEFAULT _ MODAL _ SWIPE _ CONFIG: ModalSwipeConfig = ...
const DEFAULT _ MODAL _ SWIPE _ CONFIG: ModalSwipeConfig = ...
Default swipe configuration.
Source§const DEFAULT _ MODAL _ SWIPE _ DIRECTION: SwipeDirection = 'down'
const DEFAULT _ MODAL _ SWIPE _ DIRECTION: SwipeDirection = 'down'
Source§const Modal: FC<ModalProps> = ...
const Modal: FC<ModalProps> = ...
Modal component with smooth, customizable animations and gesture support. Built on top of React Native's Modal, Reanimated, and Gesture Handler.
Functions§
Source§getSlideInDirection(
animationConfig: ModalAnimationConfigUnion,
fallback?: SwipeDirection,
): SwipeDirection
getSlideInDirection(
animationConfig: ModalAnimationConfigUnion,
fallback?: SwipeDirection,
): SwipeDirection
Gets the slide-in direction from animation config.
Source§getSwipeDirections(
swipeConfig: ModalSwipeConfig,
animationConfig?: ModalAnimationConfigUnion,
fallback?: SwipeDirection | SwipeDirection[],
): SwipeDirection[]
getSwipeDirections(
swipeConfig: ModalSwipeConfig,
animationConfig?: ModalAnimationConfigUnion,
fallback?: SwipeDirection | SwipeDirection[],
): SwipeDirection[]
Extracts swipe directions from swipe config or animation config fallback.
Source§normalizeAnimationConfig(
config?: undefined | ModalAnimation | Partial<ModalAnimationConfigUnion>,
): ModalAnimationConfigUnion
normalizeAnimationConfig(
config?: undefined | ModalAnimation | Partial<ModalAnimationConfigUnion>,
): ModalAnimationConfigUnion
Normalizes animation configuration by providing defaults for missing properties.
Source§normalizeBackdropConfig(
backdrop?: ModalBackdropConfig | ReactNode,
): {
config: ModalBackdropConfig;
customRenderer?: ReactNode;
enabled: boolean;
isCustom: boolean;
}
normalizeBackdropConfig(
backdrop?: ModalBackdropConfig | ReactNode,
): {
config: ModalBackdropConfig;
customRenderer?: ReactNode;
enabled: boolean;
isCustom: boolean;
}
Normalizes backdrop configuration by providing defaults for missing properties.
Source§normalizeSwipeConfig(config?: false | ModalSwipeConfig): ModalSwipeConfig
normalizeSwipeConfig(config?: false | ModalSwipeConfig): ModalSwipeConfig
Normalizes swipe configuration by providing defaults for missing properties.
Interfaces§
- FadeAnimationConfig
Configuration for fade animation.
- ModalBackdropConfig
Configuration for modal backdrop.
- ModalProps
Props for the Modal component.
- ModalSwipeConfig
Configuration for modal swipe.
- ScaleAnimationConfig
Configuration for scale animation.
- SlideAnimationConfig
Configuration for slide animation.
Generic type for animation config based on animation type.