react-native-modal and react-native-raw-bottom-sheet are both libraries designed to enhance user interface interactions in React Native applications by providing customizable overlay components. react-native-modal offers a flexible, animated modal that can be used for alerts, dialogs, or full-screen overlays with support for various presentation styles and gestures. react-native-raw-bottom-sheet, on the other hand, specializes in bottom sheet UIs—panels that slide up from the bottom of the screen—commonly used for action menus, pickers, or contextual content without fully interrupting the user’s current view.
Both react-native-modal and react-native-raw-bottom-sheet help you present temporary UI overlays in React Native apps, but they serve different interaction patterns and come with distinct trade-offs in flexibility, scope, and implementation. Let’s dig into how they differ in real-world usage.
react-native-modal is a general-purpose modal component. It can appear as a centered dialog, a full-screen takeover, or anything in between. It’s designed to block interaction with the underlying screen until dismissed.
import Modal from 'react-native-modal';
function MyModal() {
const [isVisible, setIsVisible] = useState(false);
return (
<>
<Button title="Show Modal" onPress={() => setIsVisible(true)} />
<Modal isVisible={isVisible} onBackdropPress={() => setIsVisible(false)}>
<View style={{ padding: 20, backgroundColor: 'white' }}>
<Text>Hello from modal!</Text>
</View>
</Modal>
</>
);
}
react-native-raw-bottom-sheet implements a bottom sheet—a panel that slides up from the bottom of the screen and often allows partial interaction with the background (e.g., tapping outside to dismiss). It’s not meant for full-screen interruptions.
import RawBottomSheet from 'react-native-raw-bottom-sheet';
function MyBottomSheet() {
const ref = useRef();
return (
<>
<Button title="Open Bottom Sheet" onPress={() => ref.current.open()} />
<RawBottomSheet ref={ref} height={200}>
<View style={{ padding: 20 }}>
<Text>Bottom sheet content</Text>
</View>
</RawBottomSheet>
</>
);
}
⚠️ Note: As of mid-2024,
react-native-raw-bottom-sheetshows signs of limited maintenance. Its GitHub repository hasn’t seen significant updates in recent years, and it lacks support for newer React Native features like Fabric or modern gesture handling. Use with caution in new projects.
react-native-modal supports multiple dismissal methods out of the box:
It also integrates smoothly with react-native-gesture-handler for advanced interactions.
<Modal
isVisible={isVisible}
onBackdropPress={() => setIsVisible(false)}
onBackButtonPress={() => setIsVisible(false)}
swipeDirection="down"
onSwipeComplete={() => setIsVisible(false)}
/>
react-native-raw-bottom-sheet relies on basic touch events and does not use react-native-gesture-handler. Dismissal is typically triggered by tapping outside or calling .close() programmatically. It doesn’t support swipe-to-dismiss natively.
// No built-in swipe support
<RawBottomSheet
ref={ref}
onClose={() => console.log('Closed')}
// Must handle backdrop taps manually if needed
>
{/* content */}
</RawBottomSheet>
This means react-native-modal offers smoother, more native-feeling interactions on both platforms, especially when complex gestures are required.
react-native-modal gives you full control over the modal’s inner content. You define the entire layout, including padding, borders, and animations. The modal itself handles visibility and backdrop styling.
<Modal
isVisible={isVisible}
style={{ justifyContent: 'flex-end', margin: 0 }} // position at bottom
backdropOpacity={0.5}
>
<View style={{ backgroundColor: 'white', padding: 16 }}>
{/* Your custom UI */}
</View>
</Modal>
You can even mimic a bottom sheet by positioning the content at the bottom.
react-native-raw-bottom-sheet requires you to specify a fixed height prop. The sheet’s container is managed internally, and you only style the children. There’s no built-in support for dynamic height based on content.
<RawBottomSheet height={250}>
{/* Children fill the sheet — you don’t control the outer container */}
</RawBottomSheet>
If your content height varies (e.g., a list that grows), react-native-raw-bottom-sheet becomes awkward to use—you’d need to calculate and update height manually.
react-native-modal includes built-in accessibility features like proper focus management and screen reader announcements. It behaves consistently across iOS and Android and respects platform conventions (e.g., safe areas, status bar handling).
react-native-raw-bottom-sheet does not mention accessibility support in its documentation. You’ll need to add accessible props and manage focus manually if required for compliance.
For apps targeting enterprise or public audiences where accessibility is non-negotiable, react-native-modal is the safer choice.
react-native-modal has no peer dependencies beyond React Native itself. It’s self-contained and works well with popular libraries like react-native-safe-area-context.
react-native-raw-bottom-sheet also has minimal dependencies, but its lack of integration with modern gesture systems means you may need to layer additional logic for smooth interactions—especially if your app already uses react-native-gesture-handler elsewhere.
Avoid react-native-raw-bottom-sheet if:
Consider alternatives like @gorhom/bottom-sheet instead.
Avoid react-native-modal only if:
But in most cases, react-native-modal’s flexibility outweighs these concerns.
react-native-modal styled to appear from the bottom—it’s more future-proof and feature-rich.react-native-modal is the clear winner.react-native-raw-bottom-sheet in a legacy app? It works for basic cases, but plan to migrate if you hit limitations.In short: unless you have a very specific reason to use react-native-raw-bottom-sheet, react-native-modal offers broader utility, better maintenance, and more robust interaction patterns for professional React Native applications.
Choose react-native-modal when you need a general-purpose, highly customizable modal that supports diverse use cases like centered dialogs, full-screen overlays, or custom animations. It integrates well with gesture handlers and provides built-in accessibility features, making it suitable for apps requiring polished, accessible modals across iOS and Android.
Choose react-native-raw-bottom-sheet if your primary need is a lightweight, no-frills bottom sheet that slides up from the bottom edge of the screen. It’s ideal for simple action sheets or temporary panels where you want minimal abstraction and direct control over layout and behavior without extra dependencies.
If you're new to the React Native world, please notice that React Native itself offers a
component that works out-of-the-box .
An enhanced, animated, customizable React Native modal.
The goal of react-native-modal is expanding the original React Native <Modal> component by adding animations, style customization options, and new features, while still providing a simple API.
This library is available on npm, install it with: npm i 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-modal:import Modal from 'react-native-modal';
<Modal> component and nest its content inside of it:function WrapperComponent() {
return (
<View>
<Modal>
<View style={{flex: 1}}>
<Text>I am the modal content!</Text>
</View>
</Modal>
</View>
);
}
isVisible prop to true:function WrapperComponent() {
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 wrapper component 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, {useState} from 'react';
import {Button, Text, View} from 'react-native';
import Modal from 'react-native-modal';
function ModalTester() {
const [isModalVisible, setModalVisible] = useState(false);
const toggleModal = () => {
setModalVisible(!isModalVisible);
};
return (
<View style={{flex: 1}}>
<Button title="Show modal" onPress={toggleModal} />
<Modal isVisible={isModalVisible}>
<View style={{flex: 1}}>
<Text>Hello!</Text>
<Button title="Hide modal" onPress={toggleModal} />
</View>
</Modal>
</View>
);
}
export default ModalTester;
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 |
coverScreen | bool | true | Will use RN Modal component to cover the entire screen wherever the modal is mounted in the component hierarchy |
hasBackdrop | bool | true | Render the backdrop |
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) |
customBackdrop | node | null | The custom backdrop element |
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 |
onModalWillHide | func | () => null | Called before the modal hide animation begins |
onModalHide | func | () => null | Called when the modal is completely hidden |
onModalWillShow | func | () => null | Called before the modal show animation begins |
onModalShow | func | () => null | Called when the modal is completely visible |
onSwipeStart | func | () => null | Called when the swipe action started |
onSwipeMove | func | (percentageShown) => null | Called on each swipe event |
onSwipeComplete | func | ({ swipingDirection }) => null | Called when the swipeThreshold has been reached |
onSwipeCancel | func | () => null | Called when the swipeThreshold has not been reached |
panResponderThreshold | number | 4 | The threshold for when the panResponder should pick up swipe events |
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 |
scrollHorizontal | bool | false | Set to true if your scrollView is horizontal (for a correct scroll handling) |
swipeThreshold | number | 100 | Swiping threshold that when reached calls onSwipeComplete |
swipeDirection | string or array | null | Defines the direction where the modal can be swiped. Can be 'up', 'down', 'left, or 'right', or a combination of them like ['up','down'] |
useNativeDriver | bool | false | Defines if animations should use native driver |
useNativeDriverForBackdrop | bool | null | Defines if animations for backdrop should use native driver (to avoid flashing on android) |
hideModalContentWhileAnimating | bool | false | Enhances the performance by hiding the modal content until the animations complete |
propagateSwipe | bool or func | false | Allows swipe events to propagate to children components (eg a ScrollView inside a modal) |
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:
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',
);
function WrapperComponent() {
const [isModalVisible, setModalVisible] = useState(true);
return (
<Modal
isVisible={isModalVisible}
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={isModalVisible}
onBackdropPress={() => setModalVisible(false)}>
<View style={{flex: 1}}>
<Text>I am the modal content!</Text>
</View>
</Modal>
The prop onSwipeComplete allows you to handle this situation (remember to set swipeDirection too!):
<Modal
isVisible={isModalVisible}
onSwipeComplete={() => setModalVisible(false)}
swipeDirection="left">
<View style={{flex: 1}}>
<Text>I am the modal content!</Text>
</View>
</Modal>
Note that when using useNativeDriver={true} the modal won't drag correctly. This is a known issue.
Unfortunately this is a known 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).
This issue has been discussed here.
The TLDR is: it's a know React-Native issue with the Modal component 😞
The modal style applied by default has a small margin.
If you want the modal to cover the entire screen you can easily override it this way:
<Modal style={{margin: 0}}>...</Modal>
Enable propagateSwipe to allow your child components to receive swipe events:
<Modal propagateSwipe>...</Modal>
Please notice that this is still a WIP fix and might not fix your issue yet, see issue #236.
Make sure your animationIn and animationOut are set correctly.
We noticed that, for example, using fadeIn as an exit animation makes the modal flicker (it should be fadeOut!).
Also, some users have noticed that setting backdropTransitionOutTiming={0} can fix the flicker without affecting the animation.
You need to specify the size of your custom backdrop component. You can also make it expand to fill the entire screen by adding a flex: 1 to its style:
<Modal isVisible={isModalVisible} customBackdrop={<View style={{flex: 1}} />}>
<View style={{flex: 1}}>
<Text>I am the modal content!</Text>
</View>
</Modal>
You can provide an event handler to the custom backdrop element to dismiss the modal. The prop onBackdropPress is not supported for a custom backdrop.
<Modal
isVisible={isModalVisible}
customBackdrop={
<TouchableWithoutFeedback onPress={dismissModalHandler}>
<View style={{flex: 1}} />
</TouchableWithoutFeedback>
}
/>
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!