@elberpg/ex-modal
v2.0.1
Published
Non-native modal for React Native / Expo — no conflicts with toasts or overlays
Maintainers
Readme
@elberpg/ex-modal
Non-native modal and drawer for React Native / Expo.
Uses position: absolute + Animated — no native Modal component, no conflicts with toasts or other overlays.
Spanish docs: README_ES.md
Preview
ExModal
| Center | Bottom sheet |
|:-:|:-:|
|
|
|
ExModalDrawer
| Left drawer | Top drawer |
|:-:|:-:|
|
|
|
Fully themeable — custom styles applied at render time.
| Square ⬛ — Center | Square ⬛ — Left drawer | Square ⬛ — Right drawer |
|:-:|:-:|:-:|
|
|
|
|
Installation
npm install @elberpg/ex-modalConcept
@elberpg/ex-modal uses a position: absolute overlay instead of React Native's native Modal component.
This means it renders as a regular view layered on top of your screen — no native modal stack, no issues with toasts or notification banners that appear above native modals.
// ExModal and ExModalDrawer sit outside your scroll/content area
return (
<>
<SafeAreaView>
<ScrollView>...</ScrollView>
</SafeAreaView>
<ExModal visible={open} onClose={() => setOpen(false)} position="center">
<MyDialog />
</ExModal>
<ExModalDrawer visible={menuOpen} onClose={() => setMenuOpen(false)} position="left">
<MyMenu />
</ExModalDrawer>
</>
);Two components are available:
ExModal— overlay for dialogs, bottom sheets, and top bannersExModalDrawer— side drawer (left, right, top, bottom)
ExModal
Animated overlay supporting center, bottom sheet, and top positions.
import { ExModal } from '@elberpg/ex-modal';
<ExModal visible={open} onClose={() => setOpen(false)} position="bottom">
<View style={styles.sheet}>
<Text>Your content here</Text>
</View>
</ExModal>Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| visible | boolean | — | Shows or hides the modal |
| onClose | () => void | — | Called when the backdrop is tapped |
| children | ReactNode | — | Modal content |
| position | 'center' \| 'top' \| 'bottom' | 'center' | Modal position on screen |
| animation | 'scale' \| 'slide' \| 'fade' | auto | Entry animation (scale for center, slide for top/bottom) |
| backdropColor | string | global style | Overrides backdrop color for this instance |
| backdropOpacity | number | 0.5 | Backdrop opacity 0–1 |
| animationDuration | number | 250 | Animation duration in ms |
| contentStyle | ViewStyle | — | Style for the content wrapper |
| closeOnBackdrop | boolean | true | Close when backdrop is tapped |
| keepMounted | boolean | false | Keep content mounted when hidden |
| renderBackdrop | ({ onClose }) => ReactNode | — | Replace the default backdrop |
ExModalDrawer
Side drawer that slides in from left, right, top, or bottom.
import { ExModalDrawer } from '@elberpg/ex-modal';
<ExModalDrawer visible={open} onClose={() => setOpen(false)} position="left" size="80%">
<View style={styles.drawer}>
<Text>Drawer content</Text>
</View>
</ExModalDrawer>Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| visible | boolean | — | Shows or hides the drawer |
| onClose | () => void | — | Called when the backdrop is tapped |
| children | ReactNode | — | Drawer content |
| position | 'left' \| 'right' \| 'top' \| 'bottom' | 'left' | Which side the drawer slides from |
| size | number \| string | '80%' | Width (left/right) or height (top/bottom) — px or '%' |
| backdropColor | string | 'rgba(0,0,0,0.4)' | Backdrop color |
| backdropOpacity | number | 1 | Backdrop opacity |
| animationDuration | number | 280 | Animation duration in ms |
| closeOnBackdrop | boolean | true | Close when backdrop is tapped |
| contentStyle | ViewStyle | — | Style applied to the drawer container |
Global styles — ExModal.styles
Override fields before the first render to apply changes app-wide.
import { ExModal } from '@elberpg/ex-modal';
ExModal.styles.backdrop = { backgroundColor: 'rgba(0,0,0,0.65)' };
ExModal.styles.inputButton = { borderRadius: 4, borderColor: '#007aff', backgroundColor: '#fff' };
ExModal.styles.inputLabel = { fontSize: 12, color: '#888' };
ExModal.styles.inputText = { fontSize: 16, color: '#000' };
ExModal.styles.inputPlaceholder = { fontSize: 16, color: '#bbb' };Available fields
| Field | Type | Description |
|-------|------|-------------|
| backdrop | { backgroundColor: string } | Semi-transparent overlay behind the modal |
| inputButton | ViewStyle | Container for an input-style trigger button |
| inputLabel | TextStyle | Label above the input button |
| inputText | TextStyle | Text showing the selected value |
| inputPlaceholder | TextStyle | Placeholder text when no value is set |
Render props
Replace any visual element with your own component.
<ExModal
renderBackdrop={({ onClose }) => (
<Pressable style={StyleSheet.absoluteFill} onPress={onClose}>
<BlurView intensity={60} style={StyleSheet.absoluteFill} />
</Pressable>
)}
>
<MyContent />
</ExModal>Important rule
Modals use position: absolute — place them outside any ScrollView.
// ✅ Correct
return (
<>
<SafeAreaView>
<ScrollView>...</ScrollView>
</SafeAreaView>
<ExModal visible={open} onClose={onClose}>
<MyContent />
</ExModal>
</>
);
// ❌ Wrong — the modal will scroll with the content
return (
<ScrollView>
<ExModal visible={open} .../>
</ScrollView>
);