A flexible, highly customizable modal component for React Native with built-in support for bottom sheets, drawers, center modals, and full-screen modals. Features smooth animations, gesture support, and TypeScript definitions.
✨ 4 Modal Types: Bottom Sheet, Drawer, Center Modal, Full-Screen
🎨 Fully Customizable: Theme colors, animations, dimensions
📱 Gesture Support: Swipe-to-dismiss for bottom sheets
⚡ Smooth Animations: Native driver for 60fps performance
🎯 TypeScript: Full type definitions included
♿ Accessible: Built with accessibility in mind
🪶 Lightweight: Zero dependencies (except React Native peer deps)
npm install @nonsobarn/react-native-versatile-modalor with yarn:
yarn add @nonsobarn/react-native-versatile-modal- React Native >= 0.60.0
- React >= 16.8.0
- iOS 11.0+
- Android API 21+
No additional native dependencies required! ✨
import React, { useState } from 'react';
import { View, Text, Button } from 'react-native';
import ModalWrapper from '@nonsobarn/react-native-versatile-modal';
function App() {
const [visible, setVisible] = useState(false);
return (
<View>
<Button title="Open Modal" onPress={() => setVisible(true)} />
<ModalWrapper visible={visible} onClose={() => setVisible(false)} type="bottom-sheet">
<View style={{ padding: 20 }}>
<Text>Hello from Modal!</Text>
</View>
</ModalWrapper>
</View>
);
}Perfect for mobile-first UIs and quick actions.
<ModalWrapper
visible={visible}
onClose={() => setVisible(false)}
type="bottom-sheet"
bottomSheetHeight="60%"
enableSwipeDown={true}
>
<YourContent />
</ModalWrapper>Traditional dialog/alert style modals.
<ModalWrapper
visible={visible}
onClose={() => setVisible(false)}
type="center"
centerModalWidth="90%"
centerModalMaxHeight="80%"
>
<YourContent />
</ModalWrapper>Side navigation or menu panels.
<ModalWrapper
visible={visible}
onClose={() => setVisible(false)}
type="drawer"
drawerPosition="left"
drawerWidth="80%"
>
<YourNavigationMenu />
</ModalWrapper>Immersive full-screen experiences.
<ModalWrapper visible={visible} onClose={() => setVisible(false)} type="full-screen">
<YourFullScreenContent />
</ModalWrapper>| Prop | Type | Default | Description |
|---|---|---|---|
visible |
boolean |
required | Controls modal visibility |
onClose |
() => void |
required | Callback when modal closes |
type |
'bottom-sheet' | 'drawer' | 'center' | 'full-screen' |
'bottom-sheet' |
Modal type |
children |
React.ReactNode |
required | Modal content |
| Prop | Type | Default | Description |
|---|---|---|---|
animationDuration |
number |
300 |
Animation duration in ms |
backdropOpacity |
number |
0.5 |
Backdrop opacity (0-1) |
| Prop | Type | Default | Description |
|---|---|---|---|
bottomSheetHeight |
number | string |
'50%' |
Height for bottom sheet |
drawerPosition |
'left' | 'right' |
'left' |
Drawer slide direction |
drawerWidth |
number | string |
'80%' |
Width for drawer |
centerModalWidth |
number | string |
'90%' |
Width for center modal |
centerModalMaxHeight |
number | string |
'80%' |
Max height for center modal |
| Prop | Type | Default | Description |
|---|---|---|---|
enableSwipeDown |
boolean |
true |
Enable swipe-to-dismiss (bottom sheet) |
enableBackdropDismiss |
boolean |
true |
Close on backdrop press |
swipeThreshold |
number |
100 |
Distance to trigger dismiss (px) |
swipeVelocityThreshold |
number |
0.5 |
Velocity to trigger dismiss |
| Prop | Type | Default | Description |
|---|---|---|---|
containerStyle |
ViewStyle |
{} |
Style for modal container |
contentStyle |
ViewStyle |
{} |
Style for content wrapper |
theme |
ThemeConfig |
{} |
Theme colors (see below) |
responsive |
ResponsiveConfig |
{} |
Responsive helpers (see below) |
| Prop | Type | Description |
|---|---|---|
onOpen |
() => void |
Called when modal starts opening |
onAnimationEnd |
() => void |
Called when animation completes |
| Prop | Type | Description |
|---|---|---|
accessibilityLabel |
string |
Accessibility label |
testID |
string |
Test identifier |
interface ThemeConfig {
backgroundColor?: string; // Modal background
backdropColor?: string; // Backdrop color
handleColor?: string; // Bottom sheet handle color
}
// Usage
<ModalWrapper
theme={{
backgroundColor: '#FFFFFF',
backdropColor: '#000000',
handleColor: '#CCCCCC',
}}
// ...
/>;Integrate with your responsive design system:
interface ResponsiveConfig {
normalize?: (value: number) => number;
normalizeWidth?: (value: number) => number;
normalizeHeight?: (value: number) => number;
}
// Example with custom responsive utility
import { normalize } from './utils/responsive';
<ModalWrapper
responsive={{
normalize: normalize,
// or separately:
// normalizeWidth: normalizeWidth,
// normalizeHeight: normalizeHeight,
}}
// ...
/>;<ModalWrapper
visible={visible}
onClose={() => setVisible(false)}
type="bottom-sheet"
bottomSheetHeight="70%"
animationDuration={400}
backdropOpacity={0.7}
theme={{
backgroundColor: '#1a1a1a',
backdropColor: '#000000',
handleColor: '#666666',
}}
containerStyle={{
borderTopLeftRadius: 30,
borderTopRightRadius: 30,
}}
>
<View style={styles.content}>
<Text style={styles.title}>Settings</Text>
{/* Your content */}
</View>
</ModalWrapper><ModalWrapper
visible={visible}
onClose={() => setVisible(false)}
type="drawer"
drawerPosition="right"
drawerWidth={300}
enableBackdropDismiss={true}
>
<NavigationMenu />
</ModalWrapper><ModalWrapper
visible={visible}
onClose={() => setVisible(false)}
type="center"
enableBackdropDismiss={false}
enableSwipeDown={false}
>
<View style={styles.alertBox}>
<Text>This action cannot be undone</Text>
<Button title="Confirm" onPress={handleConfirm} />
</View>
</ModalWrapper>Full TypeScript definitions are included:
import ModalWrapper, {
ModalType,
ModalWrapperProps,
ThemeConfig,
ResponsiveConfig,
} from '@nonsobarn/react-native-versatile-modal';- Use
useCallbackfor handlers:
const handleClose = useCallback(() => {
setVisible(false);
}, []);- Memoize heavy content:
const content = useMemo(() => <HeavyComponent />, []);- Avoid inline styles: Define styles outside render
Contributions are welcome! Please read our Contributing Guide for details.
MIT © NonsoBarn
- 🐛 Report bugs
- 💬 Discussions
- ⭐ Star this repo if you find it useful!
Made with ❤️ for the React Native community