An enhanced, animated and customizable react-native modal.
The aim of react-native-modal
is expanding the original react-native Modal
component by adding animations and styles customization options while still providing a plain-simple API.
- Smooth enter/exit animations
- Plain simple and flexible APIs
- Customizable backdrop opacity, color and timing
- Listeners for the modal animations ending
- Resize itself correctly on device rotation
- Swipeable
- Scrollable
New GIFs of your modal are warmly welcome! Feel free to submit a PR. 🎉
This library is available on npm, install it with: npm install --save react-native-modal
or yarn add react-native-modal
.
Since react-native-modal is an extension of the original react native modal, it works in a similar fashion react-native original modal.
- Import react-native-modal:
import Modal from "react-native-modal";
- Create a modal and nest its content inside of it:
render () {
return (
<View>
<Modal>
<View style={{ flex: 1 }}>
<Text>I am the modal content!</Text>
</View>
</Modal>
</View>
)
}
- Then simply show it by setting the
isVisible
prop to true:
render () {
return (
<View>
<Modal isVisible={true}>
<View style={{ flex: 1 }}>
<Text>I am the modal content!</Text>
</View>
</Modal>
</View>
)
}
The isVisible
prop is the only prop you'll really need to make the modal work: you should control this prop value by saving it in your state and setting it to true
or false
when needed.
The following example consists in a component (ModalTester
) with a button and a modal.
The modal is controlled by the isModalVisible
state variable and it is initially hidden, since its value is false
.
Pressing the button sets isModalVisible
to true, making the modal visible.
Inside the modal there is another button that, when pressed, sets isModalVisible
to false, hiding the modal.
import React, { Component } from "react";
import { Text, TouchableOpacity, View } from "react-native";
import Modal from "react-native-modal";
export default class ModalTester extends Component {
state = {
isModalVisible: false
};
_toggleModal = () =>
this.setState({ isModalVisible: !this.state.isModalVisible });
render() {
return (
<View style={{ flex: 1 }}>
<TouchableOpacity onPress={this._toggleModal}>
<Text>Show Modal</Text>
</TouchableOpacity>
<Modal isVisible={this.state.isModalVisible}>
<View style={{ flex: 1 }}>
<Text>Hello!</Text>
<TouchableOpacity onPress={this._toggleModal}>
<Text>Hide me!</Text>
</TouchableOpacity>
</View>
</Modal>
</View>
);
}
}
For a more complex example take a look at the /example
directory.
Name | Type | Default | Description |
---|---|---|---|
animationIn | string or object | 'slideInUp' | Modal show animation |
animationInTiming | number | 300 | Timing for the modal show animation (in ms) |
animationOut | string or object | 'slideOutDown' | Modal hide animation |
animationOutTiming | number | 300 | Timing for the modal hide animation (in ms) |
avoidKeyboard | bool | false | Move the modal up if the keyboard is open |
backdropColor | string | 'black' | The backdrop background color |
backdropOpacity | number | 0.70 | The backdrop opacity when the modal is visible |
backdropTransitionInTiming | number | 300 | The backdrop show timing (in ms) |
backdropTransitionOutTiming | number | 300 | The backdrop hide timing (in ms) |
children | node | REQUIRED | The modal content |
deviceHeight | number | null | Device height (useful on devices that can hide the navigation bar) |
deviceWidth | number | null | Device width (useful on devices that can hide the navigation bar) |
isVisible | bool | REQUIRED | Show the modal? |
onBackButtonPress | func | () => null | Called when the Android back button is pressed |
onBackdropPress | func | () => null | Called when the backdrop is pressed |
onModalHide | func | () => null | Called when the modal is completely hidden |
onModalShow | func | () => null | Called when the modal is completely visible |
onSwipe | func | null | Called when the swipeThreshold has been reached |
scrollOffset | number | 0 | When > 0, disables swipe-to-close, in order to implement scrollable content |
scrollOffsetMax | number | 0 | Used to implement overscroll feel when content is scrollable. See /example directory |
scrollTo | func | null | Used to implement scrollable modal. See /example directory for reference on how to use it |
swipeThreshold | number | 100 | Swiping threshold that when reached calls onSwipe |
swipeDirection | string | null | Defines the direction where the modal can be swiped (can be 'up', 'down', 'left, or 'right') |
useNativeDriver | bool | false | Defines if animations should use native driver |
hideModalContentWhileAnimating | bool | false | Enhances the performance by hiding the modal content until the animations complete |
style | any | null | Style applied to the modal |
Under the hood react-native-modal
uses react-native original Modal component.
Before reporting a bug, try swapping react-native-modal
with react-native original Modal component and, if the issue persists, check if it has already been reported as a react-native issue.
React-Native has a few issues detecting the correct device width/height of some devices.
If you're experiencing this issue, you'll need to install react-native-extra-dimensions-android
.
Then, provide the real window height (obtained from react-native-extra-dimensions-android
) to the modal:
render() {
const deviceWidth = Dimensions.get("window").width;
const deviceHeight = Platform.OS === "ios"
? Dimensions.get("window").height
: require("react-native-extra-dimensions-android").get("REAL_WINDOW_HEIGHT");
return (
<Modal
isVisible={this.state.isVisible}
deviceWidth={deviceWidth}
deviceHeight={deviceHeight}
>
<View style={{ flex: 1 }}>
<Text>I am the modal content!</Text>
</View>
</Modal>
)
}
The prop onBackdropPress
allows you to handle this situation:
<Modal
isVisible={this.state.isVisible}
onBackdropPress={() => this.setState({ isVisible: false })}
>
<View style={{ flex: 1 }}>
<Text>I am the modal content!</Text>
</View>
</Modal>
The prop onSwipe
allows you to handle this situation (remember to set swipeDirection
too!):
<Modal
isVisible={this.state.isVisible}
onSwipe={() => this.setState({ isVisible: false })}
swipeDirection="left"
>
<View style={{ flex: 1 }}>
<Text>I am the modal content!</Text>
</View>
</Modal>
Unfortunately this is a know issue that happens when useNativeDriver=true
and must still be solved.
In the meanwhile as a workaround you can set the hideModalContentWhileAnimating
prop to true
: this seems to solve the issue.
Also, do not assign a backgroundColor
property directly to the Modal. Prefer to set it on the child container.
Are you sure you named the isVisible
prop correctly? Make sure it is spelled correctly: isVisible
, not visible
.
Add a supportedOrientations={['portrait', 'landscape']}
prop to the component, as described in the React Native documentation.
Also, if you're providing the deviceHeight
and deviceWidth
props you'll have to manually update them when the layout changes.
Unfortunately right now react-native doesn't allow multiple modals to be displayed at the same time.
This means that, in react-native-modal
, if you want to immediately show a new modal after closing one you must first make sure that the modal that your closing has completed its hiding animation by using the onModalHide
prop.
See the question above. Showing multiple modals (or even alerts/dialogs) at the same time is not doable because of a react-native bug. That said, I would strongly advice against using multiple modals at the same time because, most often than not, this leads to a bad UX, especially on mobile (just my opinion).
Take a look at react-native-animatable to see the dozens of animations available out-of-the-box. You can also pass in custom animation definitions and have them automatically register with react-native-animatable. For more information on creating custom animations, see the react-native-animatable animation definition schema.
Thanks @oblador for react-native-animatable, @brentvatne for the npm namespace and to anyone who contributed to this library!
Pull requests, feedbacks and suggestions are welcome!