Important: This documentation covers Yarn 1 (Classic).
For Yarn 2+ docs and migration guide, see yarnpkg.com.

Package detail

rn-modal-bottom-sheet

christi101.5kMIT2.4.1TypeScript support: included

rn-modal-bottom-sheet - A performant, gesture-enabled bottom sheet component for React Native with snap points, scroll-to-expand, and pull-to-collapse. Zero native dependencies, works with Expo.

rn-modal-bottom-sheet, react-native, bottom-sheet, modal, sheet, modal-sheet, bottomsheet, gesture, animated, snap-points, scroll-to-expand, pull-to-collapse, drag-to-close, ios, android, expo, typescript, react-native-modal, react-native-bottom-sheet, drawer, slide-up, actionsheet, action-sheet

readme

rn-modal-bottom-sheet

npm version npm downloads GitHub stars

rn-modal-bottom-sheet is a performant, gesture-enabled bottom sheet component for React Native with snap points, scroll-to-expand, and pull-to-collapse functionality. Zero native dependencies, works seamlessly with Expo!

📱 Demo

🎥 Click here to view Demo Video - See the modal sheet in action with snap points, scroll-to-expand, and smooth animations!

npm version Platforms License TypeScript

✨ Features

  • 🎯 Snap Points - Multiple snap positions with intelligent detection
  • 📜 Scroll-to-Expand - Automatically expand to next snap point while scrolling
  • 👆 Pull-to-Collapse - Pull down at the top to collapse or close
  • 🎨 Smooth Animations - Buttery smooth bezier easing with 60fps performance
  • 🚀 High Performance - Transform-based animations, no layout recalculations
  • 🎯 Zero Native Dependencies - Built with React Native's Animated API
  • 📱 Cross Platform - Works on both iOS and Android
  • 🎭 Backdrop Animation - Independent opacity animation for backdrop
  • 👆 Gesture Support - Drag to close with customizable threshold
  • 🎨 Fully Customizable - Customize colors, dimensions, and animations
  • 📦 Lightweight - Minimal overhead, no external dependencies
  • ARIA Compliant - Full accessibility support with ARIA attributes
  • 🔒 TypeScript Support - Full TypeScript definitions included

📦 Installation

npm install rn-modal-bottom-sheet react-native-safe-area-context
# or
yarn add rn-modal-bottom-sheet react-native-safe-area-context

Peer Dependencies

This package requires the following peer dependencies:

  • react-native-safe-area-context (>=3.0.0)
  • react-native-gesture-handler (>=2.0.0) - Only needed if using enableDragAndDrop
  • react-native-reanimated (>=2.0.0) - Only needed if using SharedValue for snap points

🚀 Quick Start

Basic Usage

import React, { useRef } from 'react';
import { Button, Text, View } from 'react-native';
import ModalSheet, { ModalSheetRef } from 'rn-modal-bottom-sheet';

function App() {
  const sheetRef = useRef<ModalSheetRef>(null);

  return (
    <View style={{ flex: 1 }}>
      <Button title="Open Sheet" onPress={() => sheetRef.current?.open()} />

      <ModalSheet ref={sheetRef} height={400}>
        <Text style={{ fontSize: 24, fontWeight: 'bold', textAlign: 'center' }}>
          Hello Bottom Sheet! 👋
        </Text>
      </ModalSheet>
    </View>
  );
}

With Snap Points

import { useRef, useState } from 'react';

function MyComponent() {
  const sheetRef = useRef<ModalSheetRef>(null);
  const [currentIndex, setCurrentIndex] = useState(0);

  return (
    <ModalSheet
      ref={sheetRef}
      snapPoints={[0.3, 0.6, 0.9]} // 30%, 60%, 90% of screen height
      initialSnapIndex={0}
      onSnapPointChange={(index) => setCurrentIndex(index)}
    >
      <Text>Current snap point: {currentIndex}</Text>
    </ModalSheet>
  );
}

With Scroll-to-Expand (NEW in v2.0.0!)

import { ScrollView } from 'react-native';

<ModalSheet
  ref={sheetRef}
  snapPoints={[0.3, 0.9]}
  enableScrollToExpand={true}
  scrollExpandThreshold={20}
>
  <ScrollView
    onScroll={(e) => sheetRef.current?.handleScroll(e)}
    onScrollBeginDrag={(e) => sheetRef.current?.handleScrollBeginDrag(e)}
    onScrollEndDrag={(e) => sheetRef.current?.handleScrollEndDrag(e)}
    scrollEventThrottle={16}
  >
    {/* Your scrollable content */}
  </ScrollView>
</ModalSheet>

With Drag & Drop Support (NEW in v2.1.0!)

import DraggableFlatList from 'react-native-draggable-flatlist';

<ModalSheet
  ref={sheetRef}
  height={500}
  enableDragAndDrop={true} // Automatically wraps in GestureHandlerRootView
>
  <DraggableFlatList
    data={items}
    renderItem={renderItem}
    keyExtractor={(item) => item.key}
    onDragEnd={({ data }) => setItems(data)}
  />
</ModalSheet>

📚 API Reference

Props

Prop Type Default Description
children ReactNode Required Content to be rendered inside the bottom sheet
height number 400 Height of the bottom sheet in pixels
snapPoints number[] - Array of snap points as percentages (0-1) or pixels
initialSnapIndex number 0 Which snap point to open to initially
enableScrollToExpand boolean true Enable scroll-to-expand behavior
scrollExpandThreshold number 50 Pixels to scroll before triggering transition
enableDragAndDrop boolean false Enable automatic GestureHandlerRootView wrapping for gesture components
avoidKeyboard boolean false Enable keyboard avoidance to push sheet up when keyboard appears
keyboardOffset number 0 Additional offset when keyboard is shown (in pixels)
onSnapPointChange (index: number) => void - Callback when snap point changes
onClose () => void - Callback when the sheet is closed
onOpen () => void - Callback when the sheet is opened
backgroundColor string 'white' Background color of the sheet
borderRadius number 20 Border radius of the top corners
showHandle boolean true Show the drag handle indicator
handleColor string '#DDD' Color of the drag handle
backdropOpacity number 0.5 Opacity of the backdrop (0-1)
dragThreshold number 125 Distance to drag before sheet closes
applyBottomInset boolean true Apply bottom safe area inset padding to content
aria-label string 'Bottom sheet' Accessible label for the modal
aria-describedby string - ID of element describing the modal
backdropAriaLabel string 'Close bottom sheet' Accessible label for backdrop

Methods (via ref)

Method Description
open() Opens the bottom sheet
close() Closes the bottom sheet
present() Alias for open()
dismiss() Alias for close()
snapToPoint(index) Snap to a specific snap point by index
handleScroll(event) Process scroll events for scroll-to-expand
handleScrollBeginDrag(event) Track scroll start position
handleScrollEndDrag(event) Handle pull-to-collapse gestures

🎨 Examples

Custom Styling

<ModalSheet
  ref={sheetRef}
  height={500}
  backgroundColor="#1a1a1a"
  borderRadius={30}
  handleColor="#666"
  backdropOpacity={0.8}
>
  <Text style={{ color: 'white' }}>Dark Theme Sheet</Text>
</ModalSheet>

Music Player (Two Snap Points)

<ModalSheet
  ref={sheetRef}
  snapPoints={[0.15, 0.9]} // Mini player and full player
  initialSnapIndex={0}
  enableScrollToExpand={true}
>
  {/* Your music player UI */}
</ModalSheet>

Scrollable Content

<ModalSheet ref={sheetRef} height={600}>
  <ScrollView showsVerticalScrollIndicator={false}>
    {items.map((item) => (
      <Text key={item.id}>{item.name}</Text>
    ))}
  </ScrollView>
</ModalSheet>

With Safe Area Insets

// The bottom safe area inset is applied by default
<ModalSheet ref={sheetRef} height={400}>
  <View>
    <Text>Content with safe area padding</Text>
    {/* Bottom padding automatically applied for devices with home indicator */}
  </View>
</ModalSheet>

// Disable bottom inset if not needed
<ModalSheet ref={sheetRef} height={400} applyBottomInset={false}>
  <View>
    <Text>Content without safe area padding</Text>
  </View>
</ModalSheet>

🎯 Scroll-to-Expand Behavior

The scroll-to-expand feature allows users to naturally expand the sheet by scrolling down:

  • Gentle scroll down: Expands to next snap point
  • Medium swipe down: Jumps 2 snap points
  • Fast swipe down: Jumps to maximum height

And collapse by pulling up at the top:

  • Gentle pull up: Collapses to previous snap point
  • Fast swipe up: Closes the sheet immediately

♿ Accessibility

Fully accessible with WCAG compliance:

  • ARIA Attributes - Modern aria-label, aria-modal, aria-describedby
  • Screen Reader Support - Proper labeling for all interactive elements
  • Keyboard Navigation - Full keyboard support
  • Focus Management - Correct focus handling
  • Gesture Alternatives - Alternative methods for all gestures

🚀 Performance

  • Transform-Based: Uses translateY transforms for 60fps animations
  • Native Driver: All animations run on the UI thread
  • Smooth Easing: Custom bezier curve (0.25, 0.1, 0.25, 1)
  • No Layout Recalculations: Content pre-rendered once
  • Optimized: Efficient re-renders and memory management

📱 Platform Support

  • ✅ iOS
  • ✅ Android
  • ✅ Expo
  • ✅ React Native CLI

📄 License

MIT © Christian

☕ Support

If you find this project helpful, consider supporting:

Buy Me A Coffee


Made with ❤️ using React Native

changelog

Changelog

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

[Unreleased]

[2.4.1] - 2025-12-02

Changed

  • SEO & Discoverability Improvements: Enhanced package discoverability on npm and Google search
    • Updated package description to include full package name and key features
    • Expanded keywords from 9 to 23 terms for better search visibility
    • Added package name emphasis in README
    • Updated GitHub repository metadata and topics
    • Improved documentation for search engine optimization

[2.4.0] - 2025-11-27

Added

  • applyBottomInset Prop: New prop to automatically apply bottom safe area inset padding
    • Enabled by default (true) to handle devices with home indicators and notches
    • Automatically adds bottom padding based on device safe area insets
    • Can be disabled by setting applyBottomInset={false} when not needed
    • Requires react-native-safe-area-context (>=3.0.0) as peer dependency

Dependencies

  • Added react-native-safe-area-context as a required peer dependency (>=3.0.0)
  • Added react-native-gesture-handler as a peer dependency (>=2.0.0)
  • Added react-native-reanimated as a peer dependency (>=2.0.0)

[2.3.0] - 2025-10-14

Added

  • Context API Support: New Context API for improved state management and component integration
    • ModalSheetProvider: Context provider for managing multiple modal sheets
    • ModalSheetContext: Context for accessing modal sheet state and controls
    • useModalSheet: Custom hook for accessing modal sheet context in child components
    • Enables imperative control of modal sheets from anywhere in the component tree
    • Simplifies coordination between multiple modal sheets

Changed

  • Enhanced modular architecture with centralized state management
  • Improved TypeScript types for context and provider components

Technical Improvements

  • Better separation of concerns with Context API integration
  • Cleaner component communication patterns
  • Enhanced developer experience with hook-based API

[2.2.1] - 2025-10-13

Fixed

  • Backdrop Opacity Issue with avoidKeyboard: Fixed race condition that caused backdrop to appear washed out when using avoidKeyboard={true}
    • The keyboard animation was interrupting the modal opening animation, causing the backdrop opacity to stop mid-animation
    • Added synchronization check to ensure opening animation completes before keyboard adjustment runs
    • Backdrop now consistently reaches its target opacity regardless of keyboard timing
    • Affected users with forms and text inputs that open the keyboard immediately after modal appears

Technical Details

  • Fixed race condition in useModalAnimations hook where keyboard useEffect would start a new animation on translateY while the parallel opening animation (including backdrop opacity) was still running
  • Added isImperativelyAnimating check with interval-based waiting mechanism to prevent animation conflicts
  • Ensures backdrop opacity animation always completes to its full value before keyboard adjustments are applied

[2.2.0] - 2025-10-12

Changed

  • Major Refactoring: Reorganized codebase into modular architecture for better maintainability
    • Split component into separate modules (hooks, components, types, utils, constants)
    • Extracted animation logic into useModalAnimations hook
    • Extracted keyboard handling into useKeyboardHandler hook
    • Extracted touch/gesture handling into useTouchHandler hook
    • Extracted scroll handling into useScrollHandler hook
    • Created reusable UI components (ModalSheetHandle, ModalSheetBackdrop, ModalSheetContent)
    • Centralized constants and type definitions
    • Improved code organization and testability

Technical Improvements

  • Better separation of concerns with dedicated hooks for each feature
  • Enhanced code reusability and maintainability
  • Improved TypeScript type safety with dedicated type definitions
  • Cleaner component structure with smaller, focused modules

Notes

  • This is a non-breaking change - the public API remains exactly the same
  • All existing functionality preserved with improved internal architecture
  • No migration required for existing users

[2.1.2] - 2025-10-10

Fixed

  • Restored original single-file architecture to resolve module export issues
  • Fixed "Element type is invalid" error that occurred with modular structure
  • Cleaned build artifacts to ensure correct package distribution

[2.1.1] - 2025-10-10

Changed

  • Attempted modular refactoring (reverted in 2.1.2 due to export issues)

[2.1.0] - 2025-10-10

Added

  • enableDragAndDrop Prop: New optional prop for automatic GestureHandlerRootView wrapping
    • Set to true when using gesture-based components like DraggableFlatList
    • Eliminates need for manual GestureHandlerRootView wrapper in modal content
    • Default is false for better performance when gesture handling is not needed
    • Simplifies integration with react-native-draggable-flatlist and similar libraries

Improved

  • Cleaner API: Gesture-enabled components now work with a simple prop toggle
  • Better Performance: GestureHandlerRootView only added when explicitly needed
  • Developer Experience: Removed boilerplate code from examples

Dependencies

  • Added react-native-gesture-handler as a peer dependency (when using enableDragAndDrop)

[2.0.1] - 2025-10-09

Fixed

  • Documentation updates and minor bug fixes

[2.0.0] - 2025-10-08

Added - Major Feature: Scroll-to-Expand & Interactive Snap Points

Scroll-to-Expand Features

  • Natural Scrolling: Expand/collapse sheet by scrolling within content
  • Velocity Detection: Fast swipes jump multiple snap points or to extremes
  • Smart Boundaries: Scroll up at top to collapse, scroll down to expand
  • New Props:
    • enableScrollToExpand: Enable/disable scroll expansion (default: true)
    • scrollExpandThreshold: Pixels to scroll before triggering snap (default: 50)
  • New Ref Methods:
    • handleScroll: Process scroll events for expansion
    • handleScrollBeginDrag: Track scroll gesture start
    • handleScrollEndDrag: Detect pull-down gestures at top

Interactive Snap Points

  • Flexible Definition: Support for percentage strings (e.g., "50%") and SharedValue
  • Smooth Transitions: Bezier curve easing for natural movement
  • Keyboard Awareness: Snap points adjust when keyboard appears
  • Performance: All animations use native driver at 60fps

Breaking Changes

  • snapPoints prop now accepts (string | number)[] instead of just number[]

[1.1.0] - 2025-10-08

Added - Major Feature: Snap Points Implementation

Snap Point Features

  • Multi-Point Snapping: Supports multiple snap heights (e.g., 30%, 60%, 90%)
  • Intelligent Detection: Automatically snaps to closest snap point based on final drag position
  • Cross-Point Dragging: Can skip multiple snap points in one continuous swipe
  • Direct Close: Can close from any snap point without returning to smallest first
  • New Props:
    • snapPoints: Array of snap positions (percentage or pixels)
    • initialSnapIndex: Starting snap point index (default: 0)
    • enableScrollToExpand: Enable scroll-to-expand behavior
    • onSnapPointChange: Callback when snap point changes
  • New Ref Method: snapToPoint(index) - Programmatically snap to specific point

Architecture Improvements

  • Transform-Based Animation: Switched from height changes to translateY transforms
    • All animations use useNativeDriver: true for 60fps performance
    • No more flickering or visual glitches during transitions
  • Full Height Rendering: Modal sheet renders at maximum snap point height
    • Content is pre-rendered once and never recalculated
    • Eliminates layout recalculations during snap transitions
  • Viewport Drawer Approach: Sheet acts like a drawer sliding behind a viewport

Performance Optimizations

  • Faster Animations: Reduced from 200ms → 150ms for quicker response
  • Predictable Timing: Uses Animated.timing with Easing.out(Easing.cubic)
  • Animation Blocking: Added isAnimating state to prevent gesture conflicts
  • No Layout Recalculations: Content never re-renders during snaps
  • Native Driver: All animations run on UI thread at 60fps
  • Memory Efficient: Content rendered once, not recreated

Fixed

  • Draggable List Example: Fixed height constraint issue preventing items from rendering
    • Changed from flex: 1 to explicit height calculation
    • Removed unnecessary containerStyle from DraggableFlatList

Changed

  • Code Cleanup: Removed unused viewport wrapper and simplified structure
  • Optimized Keyboard Handling: Simplified keyboard event listeners

Breaking Changes

None - All existing props and APIs remain backward compatible

[1.0.2] - 2025-10-07

Added

  • Draggable List Example: New interactive drag & drop list example in modal sheet
    • 8 colorful draggable items with smooth animations
    • Long-press gesture to initiate drag
    • Spring-based drop animations for smooth transitions
    • Visual feedback during active drag (opacity and scale changes)
  • GestureHandlerRootView: Added to Modal component for proper gesture support
    • Fixes gesture handler compatibility issues within React Native Modal
    • Enables draggable-flatlist to work correctly inside modals
  • Enhanced Gesture Isolation: Swipe-to-close gesture now restricted to handle area only
    • Modal sheet handle area with dedicated pan responder
    • Content area remains fully interactive without gesture conflicts
    • Draggable list gestures work independently from modal dismiss gesture

Changed

  • App Root Layout: Wrapped with GestureHandlerRootView for global gesture support
  • Modal Sheet Architecture: Refactored pan responder to only apply to handle area
    • Moved from full-sheet gesture detection to handle-only detection
    • Prevents gesture conflicts with interactive content inside modal

Dependencies

  • Added react-native-draggable-flatlist (^4.0.3)
    • Provides drag-and-drop functionality for FlatList
    • Includes ScaleDecorator for smooth scale animations
    • Powered by React Native Reanimated and Gesture Handler

Technical Details

  • Animation configuration: damping: 20, stiffness: 100 for natural spring feel
  • Item dimensions: 60px height for optimal touch target and gesture response
  • Long-press delay: 70ms for quick drag initiation
  • Gesture Handler integration fully compatible with Expo workflow

Notes

Known Issues Resolved

  • Fixed: Draggable list not working inside React Native Modal
    • Solution: Added GestureHandlerRootView wrapper inside Modal component
  • Fixed: Modal swipe-to-close interfering with content gestures
    • Solution: Limited swipe gesture to handle area only

Browser/Platform Support

  • ✅ iOS - Full gesture support
  • ✅ Android - Full gesture support
  • ✅ Expo Go - Compatible with development workflow