react-native-layer v0.2.0
DocsInstallation

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
npm install @whoisrijan/react-native-layer react-native-gesture-handler react-native-reanimated react-native-worklets react-native-safe-area-context react-native-svg
yarn
yarn add @whoisrijan/react-native-layer react-native-gesture-handler react-native-reanimated react-native-worklets react-native-safe-area-context react-native-svg

iOS

terminal
cd ios && pod install
ExpoUse npx expo install for the peer dependencies so they match your SDK's support matrix.

Getting started

Setup

  1. Add the Worklets Babel plugin as the last plugin in your Babel config. Skip this on Expo, babel-preset-expo adds it for you.
  2. Wrap your app root with GestureHandlerRootView, SafeAreaProvider and, if you use toasts, ToastProvider.
  3. Rebuild the app after installing Reanimated and Worklets.
babel.config.js
module.exports = {
  presets: ['module:@react-native/babel-preset'],
  plugins: ['react-native-worklets/plugin'],
};
Root.tsx
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>
  );
}
ToastsToastProvider is only needed for toasts. Every other component works without it.
iOSiOS can't show two sibling modals at once. To open a 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.

App.tsx
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

tsx
<BottomSheet visible={visible} onClose={() => setVisible(false)}>
  <Text>Sheet content</Text>
</BottomSheet>

With drag events

tsx
<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

tsx
<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>
SizingThe sheet sizes itself from its content, so children with flex: 1 won't stretch. For long lists use a ScrollView with a maxHeight.

Props

PropTypeDefaultDescription
visiblerequiredboolean·Whether the sheet is visible.
onCloserequired() => void·Called when the sheet asks to close.
onOpen() => void·Called after the open animation finishes.
childrenrequiredReactNode·Content rendered inside the sheet.
draggablebooleantrueTurn drag gestures on or off.
enableUpwardDragbooleanfalseAllow dragging up toward full screen. Downward drag stays on while draggable is true.
showHandlebooleantrueShow or hide the drag handle.
topInsetnumberautoTop safe-area inset in px. Detected from the device.
bottomInsetnumberautoBottom safe-area inset in px. Detected from the device.
dismissThresholdnumber120Pixels the user must drag down to dismiss.
animationDurationnumber250Open and close duration in ms.
backdropOpacitynumber0.5Maximum backdrop opacity.
backdropColorstring"#000"Backdrop color.
disableBackdropClosebooleanfalseStop backdrop taps from closing the sheet.
backgroundColorstring"#fff"Sheet background color.
handleColorstring"#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.
styleViewStyle·Sheet container style.
handleStyleViewStyle·Handle bar style.
handleContainerStyleViewStyle·Handle wrapper style.
contentContainerStyleViewStyle·Content wrapper style.
backdropStyleViewStyle·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

tsx
<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

tsx
<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

PropTypeDefaultDescription
visiblerequiredboolean·Whether the modal is visible.
titlerequiredstring·Title text.
messagestring·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.
showInputbooleanfalseShow a text input for guarded confirmation.
inputLabelstring·Label above the input.
inputPlaceholderstring·Input placeholder.
validationTextstring·Exact text the user must type to enable confirm.
positiveTextstring"Confirm"Confirm button label.
negativeTextstring"Cancel"Cancel button label.
disableBackdropClosebooleanfalseStop backdrop taps from closing.
animationDurationnumber200Animation duration in ms.
backdropColorstring"rgba(0,0,0,0.4)"Backdrop color.
cardBackgroundColorstring"#fff"Card background.
titleColorstring"#111827"Title color.
messageColorstring"#6B7280"Message color.
positiveButtonColorstring"#111827"Confirm button background.
positiveTextColorstring"#fff"Confirm button text color.
negativeButtonColorstring"#F3F4F6"Cancel button background.
negativeTextColorstring"#374151"Cancel button text color.
backdropStyleViewStyle·Backdrop style.
cardStyleViewStyle·Card style.
titleStyleTextStyle·Title style.
messageStyleTextStyle·Message style.
labelStyleTextStyle·Input label style.
inputStyleViewStyle·Input style.
buttonsContainerStyleViewStyle·Buttons row style.
positiveButtonStyleViewStyle·Confirm button style.
negativeButtonStyleViewStyle·Cancel button style.
positiveStyleTextStyle·Confirm button text style.
negativeStyleTextStyle·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.

Try it in the playground →

Basic

tsx
<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.

typeIconDefault buttonColor
successCircled checkmark#16A34A
errorCircled X#DC2626
warningCircled exclamation#D97706
questionCircled question mark#2563EB
tsx
<Alert
  visible={visible}
  title="Payment Successful"
  message="Your transaction has been completed."
  type="success"
  onClose={() => setVisible(false)}
/>

Bottom position with custom colors

tsx
<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

PropTypeDefaultDescription
visiblerequiredboolean·Whether the alert is visible.
titlerequiredstring·Title text.
messagestring·Optional message body.
buttonTextstring"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.
disableBackdropClosebooleanfalseStop backdrop taps from closing.
type"success" | "error" | "warning" | "question"·Adds an icon and a default accent color.
iconSizenumber24Icon size in px. Used only when type is set.
animationDurationnumber200Animation duration in ms.
backdropColorstring"rgba(0,0,0,0.4)"Backdrop color.
backgroundColorstring"#fff"Card or sheet background.
titleColorstring"#111827"Title color.
messageColorstring"#6B7280"Message color.
buttonColorstringtype accent or "#111827"Button background. Set by type if you leave it out.
buttonTextColorstring"#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.

Try it in the playground →

1. Wrap with ToastProvider

tsx
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

tsx
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

tsx
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' }); // default
NativePass native: true to use ToastAndroid on Android. Android 11+ ignores position for native toasts, and colors aren't applied.
LengthMessages longer than 2 lines are truncated with an ellipsis.

ToastConfig

PropTypeDefaultDescription
messagerequiredstring·Text to show. Max 2 lines, then truncated.
position"top" | "center" | "bottom""bottom"Where the toast shows.
durationnumber3000How long it stays, in ms.
backgroundColorstring"#111827"Pill background.
textColorstring"#fff"Text color.
nativebooleanfalseUse 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.

Try it in the playground →
tsx
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

PropTypeDefaultDescription
visiblerequiredboolean·Whether the layer is visible.
onCloserequired() => void·Called when the layer asks to close.
onOpen() => void·Called after the open animation finishes.
childrenrequiredReactNode·Content inside the layer.
disableBackdropClosebooleanfalseStop backdrop taps from closing.
animationDurationnumber250Slide duration in ms.
backdropOpacitynumber0.5Maximum backdrop opacity.
backdropColorstring"#000"Backdrop color.
backdropStyleViewStyle·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.

tsx
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>.

tsx
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.

tsx
import { useKeyboard } from '@whoisrijan/react-native-layer';

function MyComponent() {
  const { isOpen, close } = useKeyboard();
  const handlePress = () => {
    if (isOpen) close();
  };
}

Standalone helpers for use outside components:

tsx
import { isKeyboardOpen, closeKeyboard } from '@whoisrijan/react-native-layer';

if (isKeyboardOpen()) {
  closeKeyboard();
}
InternalAll overlays call closeKeyboard() internally, so the keyboard is dismissed only when it's already open.

Reference

Types

Everything is exported for TypeScript.

tsx
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.

  1. Color props for quick changes, like backgroundColor, titleColor or buttonColor.
  2. Style overrides, full ViewStyle and TextStyle objects for exact control, like cardStyle or titleStyle.
tsx
// 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.