
Security News
Astral Launches pyx: A Python-Native Package Registry
Astral unveils pyx, a Python-native package registry in beta, designed to speed installs, enhance security, and integrate deeply with uv.
react-native-reanimated-modal
Advanced tools
A lightweight and performant modal component. Designed for smooth animations, flexibility, and minimal footprint.
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.
Or browse the code: ๐ View Example Code โ
Full API and usage documentation: ๐๏ธ View Documentation โ
npm install react-native-reanimated-modal
yarn add react-native-reanimated-modal
pnpm add react-native-reanimated-modal
bun add react-native-reanimated-modal
This library depends on the following peer dependencies:
Note: Make sure to follow the installation guides for both libraries, as they require additional platform-specific setup steps.
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);
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);
Starting from v1.1.0, we recommend using the new configuration-based API for better type safety and cleaner code:
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
};
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,
};
<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>
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.
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 |
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 |
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 |
Prop | Type | Default | Description |
---|---|---|---|
coverScreen | boolean | false | If true, covers entire screen without using native Modal |
Prop | Type | Description |
---|---|---|
onShow | () => void | Called when modal appears |
onHide | () => void | Called when modal disappears |
The component also accepts these props from React Native's Modal:
hardwareAccelerated
(Android)navigationBarTranslucent
(Android)statusBarTranslucent
(Android)onOrientationChange
(iOS)supportedOrientations
(iOS)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
};
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[]; // swipe enabled for this directions (low priority)
};
}
interface ScaleAnimationConfig {
animation: 'scale';
duration?: number;
scaleFactor?: number; // 0-1, default: 0.8
}
interface SwipeConfig {
enabled?: boolean;
directions?: SwipeDirection[], // swipe enabled for this directions (high priority)
threshold?: number;
bounceSpringConfig?: SpringConfig;
bounceOpacityThreshold?: number;
}
type ModalAnimationConfigUnion =
| FadeAnimationConfig
| SlideAnimationConfig
| ScaleAnimationConfig;
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.
<Modal
visible={visible}
animationConfig={{
animation: 'fade',
duration: 400,
}}
swipeConfig={{
directions: ['down', 'right'],
threshold: 100,
}}
onHide={() => setVisible(false)}
>
{/* Modal content */}
</Modal>
<Modal
visible={visible}
animationConfig={{
animation: 'scale',
duration: 400,
scaleFactor: 0.8,
}}
swipeConfig={{
directions: ['down', 'right'],
threshold: 100,
}}
onHide={() => setVisible(false)}
>
{/* Modal content */}
</Modal>
<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.
<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>
See the contributing guide to learn how to contribute to the repository and the development workflow.
MIT
Made with create-react-native-library
FAQs
A lightweight and performant modal component. Designed for smooth animations, flexibility, and minimal footprint.
The npm package react-native-reanimated-modal receives a total of 87 weekly downloads. As such, react-native-reanimated-modal popularity was classified as not popular.
We found that react-native-reanimated-modal demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago.ย It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Astral unveils pyx, a Python-native package registry in beta, designed to speed installs, enhance security, and integrate deeply with uv.
Security News
The Latio podcast explores how static and runtime reachability help teams prioritize exploitable vulnerabilities and streamline AppSec workflows.
Security News
The latest Opengrep releases add Apex scanning, precision rule tuning, and performance gains for open source static code analysis.