Documentation
react-native-layer docs
How to add bottom sheets, confirm modals, alerts and toasts to a React Native or Expo app. Install it, wrap your root, then pick an overlay. Every component is controlled by a visible boolean and a close callback.
Getting started
Installation
Install the package with its peer dependencies. Works with Expo and bare React Native CLI projects.
npm install @whoisrijan/react-native-layer react-native-gesture-handler react-native-reanimated react-native-worklets react-native-safe-area-context react-native-svgyarn add @whoisrijan/react-native-layer react-native-gesture-handler react-native-reanimated react-native-worklets react-native-safe-area-context react-native-svgiOS
cd ios && pod installnpx expo install for the peer dependencies so they match your SDK's support matrix.Getting started
Setup
- Add the Worklets Babel plugin as the last plugin in your Babel config. Skip this on Expo,
babel-preset-expoadds it for you. - Wrap your app root with
GestureHandlerRootView,SafeAreaProviderand, if you use toasts,ToastProvider. - Rebuild the app after installing Reanimated and Worklets.
module.exports = {
presets: ['module:@react-native/babel-preset'],
plugins: ['react-native-worklets/plugin'],
};import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import { ToastProvider } from '@whoisrijan/react-native-layer';
export default function Root() {
return (
<GestureHandlerRootView style={{ flex: 1 }}>
<SafeAreaProvider>
<ToastProvider>
<App />
</ToastProvider>
</SafeAreaProvider>
</GestureHandlerRootView>
);
}ToastProvider is only needed for toasts. Every other component works without it.ConfirmModal or Alert over a BottomSheet, render it inside the sheet's children.Getting started
Quick start
Every overlay is controlled by a visible boolean and a close callback.
import { useState } from 'react';
import { Text, Pressable, View } from 'react-native';
import { BottomSheet } from '@whoisrijan/react-native-layer';
export default function App() {
const [visible, setVisible] = useState(false);
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Pressable onPress={() => setVisible(true)}>
<Text>Open Sheet</Text>
</Pressable>
<BottomSheet visible={visible} onClose={() => setVisible(false)}>
<Text>Hello from the bottom sheet!</Text>
</BottomSheet>
</View>
);
}Component
BottomSheet
A draggable sheet that sizes to its content and follows it when content changes. Drag up to go full screen, drag down to dismiss. Safe areas are applied automatically.
Try it in the playground →Basic
<BottomSheet visible={visible} onClose={() => setVisible(false)}>
<Text>Sheet content</Text>
</BottomSheet>With drag events
<BottomSheet
visible={visible}
onClose={() => setVisible(false)}
onOpen={() => console.log('Opened')}
onDrag={(direction, fraction) =>
console.log(`Dragging ${direction} · ${Math.round(fraction * 100)}%`)
}
onDragEnd={(settled) => console.log('Settled:', settled)}
onFullScreen={() => console.log('Full screen!')}
>
<Text>Drag me up or down</Text>
</BottomSheet>Themed
<BottomSheet
visible={visible}
onClose={() => setVisible(false)}
backgroundColor="#1E1B4B"
handleColor="#A78BFA"
backdropOpacity={0.7}
contentContainerStyle={{ paddingHorizontal: 20 }}
>
<Text style={{ color: '#E0E7FF' }}>Dark themed sheet</Text>
</BottomSheet>flex: 1 won't stretch. For long lists use a ScrollView with a maxHeight.Props
| Prop | Type | Default | Description |
|---|---|---|---|
visiblerequired | boolean | · | Whether the sheet is visible. |
onCloserequired | () => void | · | Called when the sheet asks to close. |
onOpen | () => void | · | Called after the open animation finishes. |
childrenrequired | ReactNode | · | Content rendered inside the sheet. |
draggable | boolean | true | Turn drag gestures on or off. |
enableUpwardDrag | boolean | false | Allow dragging up toward full screen. Downward drag stays on while draggable is true. |
showHandle | boolean | true | Show or hide the drag handle. |
topInset | number | auto | Top safe-area inset in px. Detected from the device. |
bottomInset | number | auto | Bottom safe-area inset in px. Detected from the device. |
dismissThreshold | number | 120 | Pixels the user must drag down to dismiss. |
animationDuration | number | 250 | Open and close duration in ms. |
backdropOpacity | number | 0.5 | Maximum backdrop opacity. |
backdropColor | string | "#000" | Backdrop color. |
disableBackdropClose | boolean | false | Stop backdrop taps from closing the sheet. |
backgroundColor | string | "#fff" | Sheet background color. |
handleColor | string | "#D1D5DB" | Drag handle color. |
onDrag | (direction, fraction) => void | · | Fires while dragging. direction is "up" or "down", fraction is 0 to 1. |
onDragEnd | (settled) => void | · | Fires when a drag ends. settled is "content", "fullscreen" or "dismissed". |
onFullScreen | () => void | · | Fires when the sheet reaches full screen. |
style | ViewStyle | · | Sheet container style. |
handleStyle | ViewStyle | · | Handle bar style. |
handleContainerStyle | ViewStyle | · | Handle wrapper style. |
contentContainerStyle | ViewStyle | · | Content wrapper style. |
backdropStyle | ViewStyle | · | Backdrop style. |
Component
ConfirmModal
A centered dialog with confirm and cancel. Optionally the user has to type an exact string before confirm turns on.
Try it in the playground →Basic
<ConfirmModal
visible={visible}
title="Delete Item"
message="Are you sure? This cannot be undone."
positiveText="Delete"
positiveButtonColor="#DC2626"
negativeText="Cancel"
onCancel={() => setVisible(false)}
onConfirm={() => {
console.log('Deleted!');
setVisible(false);
}}
/>Guarded input
<ConfirmModal
visible={visible}
title="Delete Account"
showInput
inputLabel='Type "DELETE" to confirm'
inputPlaceholder="DELETE"
validationText="DELETE"
positiveText="Delete"
positiveButtonColor="#DC2626"
negativeText="Cancel"
onCancel={() => setVisible(false)}
onConfirm={(value) => {
console.log('Confirmed with:', value);
setVisible(false);
}}
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
visiblerequired | boolean | · | Whether the modal is visible. |
titlerequired | string | · | Title text. |
message | string | · | Message body. Ignored when showInput is true. |
onConfirmrequired | (value?: string) => void | · | Called on confirm. Gets the input value when showInput is true. |
onCancelrequired | () => void | · | Called on cancel, backdrop tap or back button. |
onOpen | () => void | · | Called after the open animation finishes. |
showInput | boolean | false | Show a text input for guarded confirmation. |
inputLabel | string | · | Label above the input. |
inputPlaceholder | string | · | Input placeholder. |
validationText | string | · | Exact text the user must type to enable confirm. |
positiveText | string | "Confirm" | Confirm button label. |
negativeText | string | "Cancel" | Cancel button label. |
disableBackdropClose | boolean | false | Stop backdrop taps from closing. |
animationDuration | number | 200 | Animation duration in ms. |
backdropColor | string | "rgba(0,0,0,0.4)" | Backdrop color. |
cardBackgroundColor | string | "#fff" | Card background. |
titleColor | string | "#111827" | Title color. |
messageColor | string | "#6B7280" | Message color. |
positiveButtonColor | string | "#111827" | Confirm button background. |
positiveTextColor | string | "#fff" | Confirm button text color. |
negativeButtonColor | string | "#F3F4F6" | Cancel button background. |
negativeTextColor | string | "#374151" | Cancel button text color. |
backdropStyle | ViewStyle | · | Backdrop style. |
cardStyle | ViewStyle | · | Card style. |
titleStyle | TextStyle | · | Title style. |
messageStyle | TextStyle | · | Message style. |
labelStyle | TextStyle | · | Input label style. |
inputStyle | ViewStyle | · | Input style. |
buttonsContainerStyle | ViewStyle | · | Buttons row style. |
positiveButtonStyle | ViewStyle | · | Confirm button style. |
negativeButtonStyle | ViewStyle | · | Cancel button style. |
positiveStyle | TextStyle | · | Confirm button text style. |
negativeStyle | TextStyle | · | Cancel button text style. |
Component
Alert
An alert with one dismiss button. Shows as a center card or a bottom sheet. Set type to get an icon and accent color.
Basic
<Alert
visible={visible}
title="Update Available"
message="A new version is available. Please update."
onClose={() => setVisible(false)}
/>Semantic types
When type is set, an SVG icon renders above the title and the button takes the type's accent color. You can still pass buttonColor to override it.
| type | Icon | Default buttonColor |
|---|---|---|
success | Circled checkmark | #16A34A |
error | Circled X | #DC2626 |
warning | Circled exclamation | #D97706 |
question | Circled question mark | #2563EB |
<Alert
visible={visible}
title="Payment Successful"
message="Your transaction has been completed."
type="success"
onClose={() => setVisible(false)}
/>Bottom position with custom colors
<Alert
visible={visible}
title="Connection Lost"
message="You are offline. Check your internet connection."
buttonText="Dismiss"
position="bottom"
type="error"
backgroundColor="#FEF2F2"
titleColor="#991B1B"
messageColor="#B91C1C"
buttonColor="#DC2626"
buttonTextColor="#fff"
onClose={() => setVisible(false)}
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
visiblerequired | boolean | · | Whether the alert is visible. |
titlerequired | string | · | Title text. |
message | string | · | Optional message body. |
buttonText | string | "OK" | Button label. |
onCloserequired | () => void | · | Called on button press, backdrop tap or back button. |
onOpen | () => void | · | Called after the open animation finishes. |
position | "center" | "bottom" | "center" | Where the alert shows. |
disableBackdropClose | boolean | false | Stop backdrop taps from closing. |
type | "success" | "error" | "warning" | "question" | · | Adds an icon and a default accent color. |
iconSize | number | 24 | Icon size in px. Used only when type is set. |
animationDuration | number | 200 | Animation duration in ms. |
backdropColor | string | "rgba(0,0,0,0.4)" | Backdrop color. |
backgroundColor | string | "#fff" | Card or sheet background. |
titleColor | string | "#111827" | Title color. |
messageColor | string | "#6B7280" | Message color. |
buttonColor | string | type accent or "#111827" | Button background. Set by type if you leave it out. |
buttonTextColor | string | "#fff" | Button text color. |
Provider + hook
Toast
Toasts render on top of everything in the app, including an open BottomSheet, Alert, ConfirmModal or Layer. Same custom UI on iOS and Android.
1. Wrap with ToastProvider
import { SafeAreaProvider } from 'react-native-safe-area-context';
import { ToastProvider } from '@whoisrijan/react-native-layer';
function Root() {
return (
<SafeAreaProvider>
<ToastProvider>
<App />
</ToastProvider>
</SafeAreaProvider>
);
}2. Show toasts from anywhere
import { useToast } from '@whoisrijan/react-native-layer';
function MyScreen() {
const { showToast } = useToast();
return (
<Pressable
onPress={() =>
showToast({ message: 'Item saved!', position: 'bottom', duration: 3000 })
}
>
<Text>Save</Text>
</Pressable>
);
}Themed and positioned
showToast({
message: 'Success!',
position: 'bottom',
backgroundColor: '#16A34A',
textColor: '#fff',
duration: 2000,
});
showToast({ message: 'Top!', position: 'top' });
showToast({ message: 'Center!', position: 'center' });
showToast({ message: 'Bottom!', position: 'bottom' }); // defaultnative: true to use ToastAndroid on Android. Android 11+ ignores position for native toasts, and colors aren't applied.ToastConfig
| Prop | Type | Default | Description |
|---|---|---|---|
messagerequired | string | · | Text to show. Max 2 lines, then truncated. |
position | "top" | "center" | "bottom" | "bottom" | Where the toast shows. |
duration | number | 3000 | How long it stays, in ms. |
backgroundColor | string | "#111827" | Pill background. |
textColor | string | "#fff" | Text color. |
native | boolean | false | Use ToastAndroid on Android. Ignores position and colors. |
Primitive
Layer
A low level slide-up overlay for building your own. It handles the modal, backdrop, slide animation and back button. Gesture Handler gestures work inside it without an extra GestureHandlerRootView.
import { Layer } from '@whoisrijan/react-native-layer';
<Layer visible={visible} onClose={() => setVisible(false)}>
<View style={{ flex: 1, backgroundColor: '#fff' }}>
<Text>Build anything here</Text>
</View>
</Layer>;Props
| Prop | Type | Default | Description |
|---|---|---|---|
visiblerequired | boolean | · | Whether the layer is visible. |
onCloserequired | () => void | · | Called when the layer asks to close. |
onOpen | () => void | · | Called after the open animation finishes. |
childrenrequired | ReactNode | · | Content inside the layer. |
disableBackdropClose | boolean | false | Stop backdrop taps from closing. |
animationDuration | number | 250 | Slide duration in ms. |
backdropOpacity | number | 0.5 | Maximum backdrop opacity. |
backdropColor | string | "#000" | Backdrop color. |
backdropStyle | ViewStyle | · | Backdrop style. |
Reference
Hooks
useInsets
Returns the device safe-area insets. A thin wrapper around useSafeAreaInsets. BottomSheet already uses it, so you only need it for custom overlays.
import { useInsets } from '@whoisrijan/react-native-layer';
function MyComponent() {
const insets = useInsets();
// insets.top, insets.bottom, insets.left, insets.right
}useToast
Returns { showToast }. Must be used inside <ToastProvider>.
const { showToast } = useToast();
showToast({ message: 'Hello!' });useKeyboard
Keyboard state plus a safe dismiss. It only closes the keyboard when it's actually open, which avoids a flash open then close on some devices.
import { useKeyboard } from '@whoisrijan/react-native-layer';
function MyComponent() {
const { isOpen, close } = useKeyboard();
const handlePress = () => {
if (isOpen) close();
};
}Standalone helpers for use outside components:
import { isKeyboardOpen, closeKeyboard } from '@whoisrijan/react-native-layer';
if (isKeyboardOpen()) {
closeKeyboard();
}closeKeyboard() internally, so the keyboard is dismissed only when it's already open.Reference
Types
Everything is exported for TypeScript.
import type {
BottomSheetProps,
ConfirmModalProps,
AlertProps,
AlertPosition, // 'center' | 'bottom'
AlertType, // 'success' | 'error' | 'warning' | 'question'
ToastConfig,
ToastContextValue,
ToastPosition, // 'top' | 'center' | 'bottom'
LayerProps,
DragDirection, // 'up' | 'down'
Insets, // { top, bottom, left, right }
} from '@whoisrijan/react-native-layer';Reference
Theming
Every component has two levels of customization.
- Color props for quick changes, like
backgroundColor,titleColororbuttonColor. - Style overrides, full
ViewStyleandTextStyleobjects for exact control, likecardStyleortitleStyle.
// Quick theming with color props
<BottomSheet
visible={visible}
onClose={close}
backgroundColor="#1E1B4B"
handleColor="#A78BFA"
backdropOpacity={0.7}
>
{content}
</BottomSheet>
// Full control with style overrides
<ConfirmModal
visible={visible}
title="Custom"
onConfirm={confirm}
onCancel={cancel}
cardStyle={{ borderRadius: 24, padding: 32 }}
titleStyle={{ fontSize: 22, fontWeight: '800' }}
positiveButtonStyle={{ borderRadius: 20 }}
/>Missing a prop you need? Contributions are welcome on GitHub.