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)
Note: Make sure to follow the installation guides for both libraries, as they require additional platform-specific setup steps.
Important Setup
Make sure to wrap your root App component with gestureHandlerRootHOC
for gesture handling to work properly:
import { gestureHandlerRootHOC } from 'react-native-gesture-handler';
const App = () => {
{/* Your app */}
};
export default gestureHandlerRootHOC(App);
🚀 Basic Usage
import React, { useState } from 'react';
import { View, Text, Button } from 'react-native';
import { gestureHandlerRootHOC } from 'react-native-gesture-handler';
import { Modal } from 'react-native-reanimated-modal';
const App = () => {
const [visible, setVisible] = useState(false);
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Button title="Show Modal" onPress={() => setVisible(true)} />
<Modal
visible={visible}
onHide={() => setVisible(false)}
animationConfig={{
animation: 'scale',
duration: 400,
scaleFactor: 0.8,
}}
swipeConfig={{
enabled: true,
directions: ['down', 'left', 'right'],
threshold: 100,
}}
>
<View style={{
backgroundColor: 'white',
padding: 20,
borderRadius: 10,
margin: 20
}}>
<Text>Hello from Modal!</Text>
<Button title="Close" onPress={() => setVisible(false)} />
</View>
</Modal>
</View>
);
};
export default gestureHandlerRootHOC(App);
📖 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'> = {
animation: 'scale',
duration: 400,
scaleFactor: 0.8, // Start from 80% size
};
// Fade animation
const fadeConfig: ModalAnimationConfig<'fade'> = {
animation: 'fade',
duration: 300,
};
// Slide animation with complex directions
const slideConfig: ModalAnimationConfig<'slide'> = {
animation: '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'> = {
animation: 'slide',
duration: 400,
direction: 'up', // Both slide-in and dismiss direction
};
Swipe Configurations
import type { SwipeConfig } from 'react-native-reanimated-modal';
// Basic swipe config
const basicSwipe: SwipeConfig = {
enabled: true,
directions: ['down', 'left', 'right'], // Allow swiping in these directions
threshold: 120,
};
// Advanced swipe config with custom bounce
const advancedSwipe: SwipeConfig = {
enabled: true,
directions: ['up', 'down'], // Only vertical swipes
threshold: 80,
bounceSpringConfig: {
stiffness: 300,
dampingRatio: 0.7,
duration: 400,
},
bounceOpacityThreshold: 0.1,
};
// Disabled swipe
const noSwipe: SwipeConfig = {
enabled: false,
};
Usage Examples
<Modal
visible={visible}
animationConfig={scaleConfig}
swipeConfig={advancedSwipe}
>
{/* Your content */}
</Modal>
// Or with inline configs
<Modal
visible={visible}
animationConfig={{
animation: 'scale',
duration: 600,
scaleFactor: 0.9,
}}
swipeConfig={{
enabled: true,
threshold: 100,
}}
>
{/* Your content */}
</Modal>
// Legacy string syntax still supported
<Modal
visible={visible}
animationConfig="fade" // Equivalent to { animation: '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 |
renderBackdrop |
() => ReactNode |
- | Custom backdrop renderer |
Configuration Props
Prop | Type | Default | Description |
---|---|---|---|
animationConfig |
ModalAnimationConfigUnion | ModalAnimation |
{ animation: 'fade', duration: 300 } |
Animation configuration object or simple animation type string |
swipeConfig |
SwipeConfig |
{ enabled: true, directions: ['down'], threshold: 100 } |
Swipe gesture configuration |
Backdrop Props
Prop | Type | Default | Description |
---|---|---|---|
hasBackdrop |
boolean |
true |
Whether to show backdrop behind modal |
backdropColor |
string |
'black' |
Color of the backdrop |
backdropOpacity |
number |
0.7 |
Opacity of the backdrop (0-1) |
onBackdropPress |
() => void |
- | Callback when backdrop is pressed |
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_OPACITY, // 0.7
DEFAULT_MODAL_BACKDROP_COLOR, // 'black'
DEFAULT_MODAL_SWIPE_THRESHOLD, // 100
DEFAULT_MODAL_BOUNCE_SPRING_CONFIG, // { stiffness: 200, 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 = {
animation: 'scale',
duration: DEFAULT_MODAL_ANIMATION_DURATION * 2, // 600ms
scaleFactor: DEFAULT_MODAL_SCALE_FACTOR, // 0.8
};
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 {
animation: 'fade';
duration?: number;
}
interface SlideAnimationConfig {
animation: 'slide';
duration?: number;
direction?: SwipeDirection | {
start: SwipeDirection;
end: SwipeDirection | SwipeDirection[];
};
}
interface ScaleAnimationConfig {
animation: 'scale';
duration?: number;
scaleFactor?: number; // 0-1, default: 0.8
}
interface SwipeConfig {
enabled?: boolean;
threshold?: number;
bounceSpringConfig?: SpringConfig;
bounceOpacityThreshold?: 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
onBackdropPress={() => setFirstModalVisible(false)}
>
{/* First modal content */}
</Modal>
<Modal
visible={secondModalVisible}
coverScreen // Important: excludes native Modal usage
onBackdropPress={() => 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}
animationConfig={{
animation: 'fade',
duration: 400,
}}
swipeConfig={{
directions: ['down', 'right'],
threshold: 100,
}}
onHide={() => setVisible(false)}
>
{/* Modal content */}
</Modal>
Scale Animation with Custom Duration
<Modal
visible={visible}
animationConfig={{
animation: 'scale',
duration: 400,
scaleFactor: 0.8,
}}
swipeConfig={{
directions: ['down', 'right'],
threshold: 100,
}}
onHide={() => setVisible(false)}
>
{/* Modal content */}
</Modal>
Custom Slide Animation with Swipe Directions
<Modal
visible={visible}
animationConfig={{
animation: 'slide',
duration: 500,
direction: {
start: 'down', // Slides in from bottom
end: ['down', 'right'], // Can dismiss by swiping down or right
},
}}
swipeConfig={{
threshold: 150,
bounceSpringConfig: {
stiffness: 300,
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 }}
animationConfig={{
animation: 'slide',
duration: 300,
direction: 'down',
}}
swipeConfig={{
directions: ['down'],
threshold: 80,
}}
hasBackdrop={false} // No backdrop for full screen
onHide={() => setVisible(false)}
>
{/* 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 _ BOUNCE _ SPRING _ CONFIG: { dampingRatio: 0.5; duration: 700; stiffness: 200 } = ...
const DEFAULT _ MODAL _ BOUNCE _ SPRING _ CONFIG: { dampingRatio: 0.5; duration: 700; stiffness: 200 } = ...
Source§const DEFAULT _ MODAL _ SWIPE _ CONFIG: SwipeConfig = ...
const DEFAULT _ MODAL _ SWIPE _ CONFIG: SwipeConfig = ...
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: SwipeConfig,
animationConfig?: ModalAnimationConfigUnion,
fallback?: SwipeDirection | SwipeDirection[],
): SwipeDirection[]
getSwipeDirections(
swipeConfig: SwipeConfig,
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§normalizeSwipeConfig(config?: Partial<SwipeConfig>): SwipeConfig
normalizeSwipeConfig(config?: Partial<SwipeConfig>): SwipeConfig
Normalizes swipe configuration by providing defaults for missing properties.
Interfaces§
- FadeAnimationConfig
Configuration for fade animation.
- ModalProps
Props for the Modal component.
- ScaleAnimationConfig
Configuration for scale animation.
- SlideAnimationConfig
Configuration for slide animation.
- SwipeConfig
Configuration for swipe gestures.
Generic type for animation config based on animation type.