Skip to main content

Gorhom Bottom Sheet vs Expo Bottom Sheet vs RNSBS 2026

·PkgPulse Team

Gorhom Bottom Sheet vs Expo Bottom Sheet vs RNSBS 2026

TL;DR

Bottom sheets are one of the most-used mobile UI patterns — filter panels, share menus, detail views — and the React Native ecosystem has multiple solid options. Gorhom Bottom Sheet v5 is the most feature-complete: Reanimated-powered smooth animations, gesture-controlled dragging, snap points, dynamic content sizing, and a BottomSheetFlatList/BottomSheetScrollView that properly handles scrollable content inside sheets. Expo Bottom Sheet is a wrapper around Gorhom built for the Expo/expo-ui ecosystem — simplifies the API for common use cases and integrates with the Expo design system. React Native Simple Bottom Sheet (RNSBS) is the lightweight option — minimal dependencies, simple snap-to-open/close behavior, and easy integration when you just need a basic sheet without the full Gorhom power. For complex sheets with scrollable content, multiple snap points, and gestures: Gorhom. For Expo-native apps wanting a simpler API: Expo Bottom Sheet. For simple, minimal-dependency sheets: RNSBS.

Key Takeaways

  • Gorhom v5 uses Reanimated 3 — worklet-based animations run on UI thread, not JS thread
  • Gorhom supports dynamic sizingenableDynamicSizing measures content height automatically
  • BottomSheetFlatList — scroll-inside-sheet with proper gesture discrimination
  • Snap points["25%", "50%", "90%"] or pixel values [300, 600]
  • Backdrop component — dimmed overlay that dismisses sheet on tap
  • Expo Bottom Sheet uses Gorhom under the hood — same performance characteristics
  • RNSBS has no Reanimated dependency — uses React Native Animated API

Use Case Guide

Scrollable list inside sheet      → Gorhom BottomSheetFlatList
Dynamic height (auto-size)        → Gorhom enableDynamicSizing
Multiple snap positions           → Gorhom snapPoints prop
Simple open/close sheet           → RNSBS or Expo Bottom Sheet
Expo project (managed workflow)   → Expo Bottom Sheet
Filter panel with form            → Gorhom BottomSheetScrollView
Confirmation / action sheet       → Any (RNSBS simplest)
Full-screen drawer behavior       → Gorhom at snapPoints={["100%"]}

Gorhom Bottom Sheet v5

The industry standard for React Native bottom sheets — smooth Reanimated animations, gesture-based dragging, and first-class support for scrollable content inside the sheet.

Installation

npm install @gorhom/bottom-sheet@5
# Required dependencies:
npm install react-native-reanimated react-native-gesture-handler
npx pod-install  # iOS

Gesture Handler Setup

// app/_layout.tsx or App.tsx
import { GestureHandlerRootView } from "react-native-gesture-handler";

export default function RootLayout() {
  return (
    <GestureHandlerRootView style={{ flex: 1 }}>
      {/* rest of app */}
    </GestureHandlerRootView>
  );
}

Basic Bottom Sheet

import { useRef, useCallback } from "react";
import { View, Text, Button, StyleSheet } from "react-native";
import BottomSheet, { BottomSheetView } from "@gorhom/bottom-sheet";

export function FilterPanel() {
  const bottomSheetRef = useRef<BottomSheet>(null);

  // Snap to 50% on open, dismiss on close
  const snapPoints = ["50%", "90%"];

  const handleOpen = useCallback(() => {
    bottomSheetRef.current?.expand();
  }, []);

  const handleClose = useCallback(() => {
    bottomSheetRef.current?.close();
  }, []);

  const handleSheetChanges = useCallback((index: number) => {
    console.log("Sheet position changed to index:", index);
  }, []);

  return (
    <View style={styles.container}>
      <Button title="Open Filters" onPress={handleOpen} />

      <BottomSheet
        ref={bottomSheetRef}
        index={-1}              // -1 = closed (hidden)
        snapPoints={snapPoints}
        onChange={handleSheetChanges}
        enablePanDownToClose={true}
      >
        <BottomSheetView style={styles.contentContainer}>
          <Text style={styles.title}>Filters</Text>
          <Button title="Apply" onPress={handleClose} />
        </BottomSheetView>
      </BottomSheet>
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1 },
  contentContainer: { flex: 1, padding: 20, alignItems: "center" },
  title: { fontSize: 18, fontWeight: "bold", marginBottom: 16 },
});

Backdrop (Dimmed Overlay)

import BottomSheet, { BottomSheetBackdrop, BottomSheetView } from "@gorhom/bottom-sheet";
import { useCallback } from "react";

export function BottomSheetWithBackdrop() {
  const renderBackdrop = useCallback(
    (props: any) => (
      <BottomSheetBackdrop
        {...props}
        disappearsOnIndex={-1}
        appearsOnIndex={0}
        pressBehavior="close"   // Tap backdrop to close
        opacity={0.5}
      />
    ),
    []
  );

  return (
    <BottomSheet
      index={-1}
      snapPoints={["50%"]}
      backdropComponent={renderBackdrop}
      enablePanDownToClose
    >
      <BottomSheetView style={{ padding: 20 }}>
        <Text>Sheet content</Text>
      </BottomSheetView>
    </BottomSheet>
  );
}

Scrollable Content (BottomSheetFlatList)

import BottomSheet, { BottomSheetFlatList } from "@gorhom/bottom-sheet";

interface Product {
  id: string;
  name: string;
  price: number;
}

export function ProductSheet({ products }: { products: Product[] }) {
  const renderItem = useCallback(
    ({ item }: { item: Product }) => (
      <View style={styles.item}>
        <Text>{item.name}</Text>
        <Text>${item.price}</Text>
      </View>
    ),
    []
  );

  return (
    <BottomSheet
      index={0}
      snapPoints={["50%", "90%"]}
      enablePanDownToClose
    >
      {/* BottomSheetFlatList handles gesture discrimination
          between sheet drag and list scroll automatically */}
      <BottomSheetFlatList
        data={products}
        keyExtractor={(item) => item.id}
        renderItem={renderItem}
        contentContainerStyle={styles.listContent}
      />
    </BottomSheet>
  );
}

Dynamic Height (Auto-Size Content)

import BottomSheet, { BottomSheetView } from "@gorhom/bottom-sheet";

export function AutoSizedSheet() {
  return (
    <BottomSheet
      index={0}
      enableDynamicSizing={true}   // Height matches content
      enablePanDownToClose
    >
      <BottomSheetView>
        {/* Sheet height adjusts to fit this content */}
        <Text style={{ padding: 20, fontSize: 16 }}>
          This sheet auto-sizes to fit its content.
        </Text>
        <View style={{ padding: 20 }}>
          <Button title="Action 1" onPress={() => {}} />
          <Button title="Action 2" onPress={() => {}} />
          <Button title="Cancel" onPress={() => {}} />
        </View>
      </BottomSheetView>
    </BottomSheet>
  );
}

BottomSheetModal (Global Modal System)

import { useCallback, useRef } from "react";
import {
  BottomSheetModal,
  BottomSheetModalProvider,
  BottomSheetView,
} from "@gorhom/bottom-sheet";

// Wrap app with BottomSheetModalProvider
export function AppWithModalProvider() {
  return (
    <GestureHandlerRootView style={{ flex: 1 }}>
      <BottomSheetModalProvider>
        <App />
      </BottomSheetModalProvider>
    </GestureHandlerRootView>
  );
}

// Use the modal from any component
function ProductCard({ product }: { product: Product }) {
  const modalRef = useRef<BottomSheetModal>(null);

  const handlePresent = useCallback(() => {
    modalRef.current?.present();
  }, []);

  const handleDismiss = useCallback(() => {
    modalRef.current?.dismiss();
  }, []);

  return (
    <View>
      <TouchableOpacity onPress={handlePresent}>
        <Text>{product.name}</Text>
      </TouchableOpacity>

      <BottomSheetModal
        ref={modalRef}
        snapPoints={["60%"]}
        enablePanDownToClose
      >
        <BottomSheetView style={{ padding: 20 }}>
          <Text style={{ fontSize: 20, fontWeight: "bold" }}>{product.name}</Text>
          <Text>Price: ${product.price}</Text>
          <Button title="Add to Cart" onPress={handleDismiss} />
        </BottomSheetView>
      </BottomSheetModal>
    </View>
  );
}

Expo Bottom Sheet

Expo's bottom sheet component from the expo-ui ecosystem — a simpler API wrapping Gorhom for common Expo use cases.

Installation

npx expo install expo-bottom-sheet

Usage

import { BottomSheet, BottomSheetView } from "expo-bottom-sheet";
import { useState } from "react";

export function SimpleSheet() {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <>
      <Button title="Open" onPress={() => setIsOpen(true)} />

      <BottomSheet
        isOpen={isOpen}
        onClose={() => setIsOpen(false)}
      >
        <BottomSheetView>
          <Text style={{ padding: 20 }}>Sheet content here</Text>
          <Button title="Close" onPress={() => setIsOpen(false)} />
        </BottomSheetView>
      </BottomSheet>
    </>
  );
}

React Native Simple Bottom Sheet (RNSBS)

A lightweight bottom sheet with no Reanimated dependency — uses React Native's built-in Animated API for a simple open/close behavior.

Installation

npm install rn-simple-bottom-sheet

Usage

import RNSimpleBottomSheet from "rn-simple-bottom-sheet";
import { useRef } from "react";

export function SimpleSheet() {
  const sheetRef = useRef<RNSimpleBottomSheet>(null);

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

      <RNSimpleBottomSheet
        ref={sheetRef}
        height={300}
        draggable={true}
        onClose={() => console.log("Sheet closed")}
      >
        <View style={{ padding: 20 }}>
          <Text>Simple sheet content</Text>
          <Button title="Close" onPress={() => sheetRef.current?.close()} />
        </View>
      </RNSimpleBottomSheet>
    </View>
  );
}

Feature Comparison

FeatureGorhom v5Expo Bottom SheetRNSBS
Animation engineReanimated 3 (UI thread)Reanimated 3RN Animated (JS thread)
Snap points✅ Multiple❌ (height only)
Dynamic sizing✅ enableDynamicSizing
Scrollable content✅ BottomSheetFlatList⚠️ Manual
Backdrop✅ BottomSheetBackdrop
BottomSheetModal✅ (global modal system)
Gesture pan-to-dismiss
Expo Go compatible❌ (Reanimated)
Setup complexityMedium (GestureHandler)LowLow
Bundle sizeLargeMediumSmall
TypeScript✅ Excellent
GitHub stars7.5kN/A (new)0.5k
npm weekly700kGrowing20k

When to Use Each

Choose Gorhom Bottom Sheet if:

  • Scrollable list/FlatList inside the sheet (proper gesture discrimination)
  • Multiple snap points (25%, 50%, 90%) for expanding/collapsing
  • enableDynamicSizing to fit variable-height content
  • BottomSheetModal for a centralized modal system
  • Full control over backdrop, handle, and animation

Choose Expo Bottom Sheet if:

  • Building with Expo SDK and want API consistency with expo-ui components
  • Standard open/close behavior is sufficient — no complex snap points needed
  • Prefer Expo's managed approach to native modules

Choose RNSBS if:

  • Zero tolerance for Reanimated/Gesture Handler peer dependencies
  • Works in Expo Go (no bare workflow needed)
  • Simple open/close action sheet behavior — no scrollable content
  • Lightweight dependencies are a priority

Methodology

Data sourced from Gorhom Bottom Sheet documentation (gorhom.github.io/react-native-bottom-sheet), Expo Bottom Sheet documentation (docs.expo.dev), React Native Simple Bottom Sheet GitHub repository, npm download statistics as of February 2026, GitHub star counts as of February 2026, and community discussions from the React Native Discord and r/reactnative.


Related: FlashList vs FlatList vs LegendList for the high-performance list components used inside BottomSheetFlatList, or React Native Reanimated vs Moti vs Skia for the animation library that powers Gorhom's smooth sheet transitions.

Comments

Stay Updated

Get the latest package insights, npm trends, and tooling tips delivered to your inbox.