npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@deskbtm/react-native-bottom-sheet

v0.0.2-alpha.0

Published

React Native bottom sheet with iOS presentation and custom push host modes, modal overlay, snap points, keyboard and scroll handoff

Readme

@deskbtm/react-native-bottom-sheet

CI npm version license: MIT

React Native bottom sheet built around two first-class host layouts: presentation scales and letterboxes the app like an iOS sheet modal, and push lifts the entire host behind the sheet with rounded corners and side insets. Also supports modal overlay, snap points, keyboard handling, and scroll-to-pan handoff.

Platforms: iOS · Android · Web

https://github.com/user-attachments/assets/0bd5b540-7a72-48fc-a316-31c5a0c2a557

Features

  • Presentation mode — iOS-style host scale + letterbox; stacked card peek when multiple sheets open
  • Push mode — custom host lift layout: app slides up behind the sheet with matched corner radius and horizontal inset
  • Modal mode — classic dimmed scrim overlay; app stays full size
  • Imperative & declarative APIsuseBottomSheet().present() and BottomSheetModal
  • Snap points — presets (medium, large, full), percentages ('50%'), or fractions
  • Scroll handoffBottomSheetScrollView, BottomSheetFlatList, BottomSheetSectionList
  • Keyboard-awareBottomSheetTextInput + keyboardBehavior: 'interactive'
  • Multi-sheet stack — iOS card stacking in presentation mode
  • Dynamic sizingenableDynamicSizing for content-driven height

Table of contents

Installation

From npm

npm install @deskbtm/react-native-bottom-sheet
# or
pnpm add @deskbtm/react-native-bottom-sheet
# or
yarn add @deskbtm/react-native-bottom-sheet

From GitHub

Install a branch, tag, or commit directly from the repository. The prepare script compiles TypeScript during install (npm installs devDependencies for git dependencies by default).

npm install github:deskbtm/react-native-bottom-sheet
# pin a ref
npm install github:deskbtm/react-native-bottom-sheet#main
npm install github:deskbtm/react-native-bottom-sheet#v0.0.1

pnpm add github:deskbtm/react-native-bottom-sheet
yarn add github:deskbtm/react-native-bottom-sheet

Use the git+https:// form if your Git is configured to rewrite HTTPS URLs to SSH:

npm install "git+https://github.com/deskbtm/react-native-bottom-sheet.git#main"

Install peer dependencies (Expo projects):

npx expo install react-native-gesture-handler react-native-reanimated \
  react-native-keyboard-controller react-native-safe-area-context react-native-worklets

| Peer dependency | Purpose | | ---------------------------------- | ----------------------- | | expo | Expo module autolinking | | react-native-gesture-handler | Pan gestures | | react-native-reanimated | UI-thread animations | | react-native-keyboard-controller | Keyboard avoidance | | react-native-safe-area-context | Safe area insets | | react-native-worklets | Reanimated worklets |

Setup

Wrap your app root with the required providers, then add BottomSheetProvider:

import { DarkTheme, DefaultTheme } from '@react-navigation/native';
import { useColorScheme } from 'react-native';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { KeyboardProvider } from 'react-native-keyboard-controller';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import { BottomSheetProvider } from '@deskbtm/react-native-bottom-sheet';

export function App() {
	const theme = useColorScheme() === 'dark' ? DarkTheme : DefaultTheme;

	return (
		<GestureHandlerRootView style={{ flex: 1 }}>
			<SafeAreaProvider>
				<KeyboardProvider preload={false}>
					<BottomSheetProvider
						mode="presentation"
						layout={{ presentation: { cornerRadius: 32 } }}
					>
						<Navigation theme={theme} />
					</BottomSheetProvider>
				</KeyboardProvider>
			</SafeAreaProvider>
		</GestureHandlerRootView>
	);
}

React Navigation theme: Pass theme to your navigator so screens get an opaque colors.background. In presentation mode this keeps the host page visible behind the sheet (only the letterbox bars are black). Without a theme or explicit screen background, host views may appear transparent.

Usage

Imperative present()

Call present() from any component inside BottomSheetProvider:

import { Pressable, Text, View } from 'react-native';
import { useBottomSheet } from '@deskbtm/react-native-bottom-sheet';

function HomeScreen() {
	const { present, dismiss, dismissAll, isPresented } = useBottomSheet();

	return (
		<View>
			<Pressable
				onPress={() =>
					present(
						<View style={{ padding: 24 }}>
							<Text>Sheet content</Text>
						</View>,
						{
							snapPoints: ['40%', '85%'],
							index: 0,
							enablePanDownToClose: true,
						},
					)
				}
			>
				<Text>Open sheet</Text>
			</Pressable>

			<Pressable onPress={() => dismiss()}>
				<Text>Dismiss top</Text>
			</Pressable>
			<Pressable onPress={() => dismissAll()}>
				<Text>Dismiss all</Text>
			</Pressable>
		</View>
	);
}

present() returns a sheet id. Pass it to dismiss(id) to close a specific sheet.

Declarative BottomSheetModal

Mount once, open with a ref — useful for sheets tied to a screen lifecycle:

import { useRef } from 'react';
import { Button } from 'react-native';
import {
	BottomSheetModal,
	BottomSheetScrollView,
	type BottomSheetModalRef,
} from '@deskbtm/react-native-bottom-sheet';

function ProfileScreen() {
	const sheetRef = useRef<BottomSheetModalRef>(null);

	return (
		<>
			<Button title="Edit profile" onPress={() => sheetRef.current?.present()} />

			<BottomSheetModal
				ref={sheetRef}
				sheetId="edit-profile"
				snapPoints={['40%', '90%']}
				index={0}
				keyboardBehavior="interactive"
				onDismiss={() => console.log('closed')}
			>
				<BottomSheetScrollView contentContainerStyle={{ padding: 16 }}>
					{/* sheet body */}
				</BottomSheetScrollView>
			</BottomSheetModal>
		</>
	);
}

Ref methods: present, dismiss, close, forceClose, dismissAll, snapToIndex, expand, collapse.

Presentation modes

Override per sheet with mode in options, or set a default on the provider:

| Mode | Behavior | | -------------- | ------------------------------------------------------------------------------------------------ | | presentation | iOS-style: host scales down, letterbox bars, stacked card peek | | modal | Standard overlay with dimmed scrim; host stays full size | | push | Custom host-lift layout: app moves up behind the sheet with matched corner radius and side inset |

// Push mode — host slides up with the sheet
present(<PushSheetContent />, {
	mode: 'push',
	snapPoints: ['35%', '70%', 'full'],
	index: 0,
});

// Modal mode — flat overlay
present(<ModalContent />, {
	mode: 'modal',
	snapPoints: ['45%'],
	scrimColor: 'rgba(0,0,0,0.45)',
	dismissOnScrimPress: true,
});

Scrollable content

Use library scrollables inside sheets — raw React Native ScrollView / FlatList do not participate in scroll-to-pan handoff:

import {
	BottomSheetScrollView,
	BottomSheetFlatList,
	BottomSheetTextInput,
	useBottomSheetContent,
} from '@deskbtm/react-native-bottom-sheet';

function ScrollableSheet() {
	const { close, expand, collapse } = useBottomSheetContent();

	return (
		<BottomSheetScrollView contentContainerStyle={{ padding: 16 }}>
			<BottomSheetTextInput
				placeholder="Type here"
				style={{ borderWidth: 1, padding: 12 }}
			/>
			{/* rows… */}
		</BottomSheetScrollView>
	);
}

// Open with content panning enabled
present(<ScrollableSheet />, {
	snapPoints: ['50%', '90%'],
	enableContentPanningGesture: true,
});

Available content primitives:

| Export | Use for | | ------------------------ | ---------------------------- | | BottomSheetView | Static layout | | BottomSheetScrollView | Scrollable content | | BottomSheetFlatList | Long lists | | BottomSheetSectionList | Sectioned lists | | BottomSheetFlashList | Currently aliases FlatList | | BottomSheetTextInput | Keyboard-aware input |

Inside sheet content, use useBottomSheetContent() for close, expand, collapse, and animated values.

Keyboard

Pair BottomSheetTextInput with interactive keyboard behavior:

present(<FormSheet />, {
	snapPoints: ['50%', '90%'],
	keyboardBehavior: 'interactive',
	keyboardBlurBehavior: 'restore',
});

| keyboardBehavior | Effect | | ------------------ | ------------------------------------------ | | interactive | Sheet tracks keyboard while typing | | fillParent | Sheet fills available space above keyboard | | extend | Extends sheet height when keyboard opens |

Stacking sheets

Each present() pushes another sheet onto the overlay stack. Host motion follows the bottom sheet. In presentation mode, buried sheets show iOS-style card inset and scale.

present(<SheetA />, { snapPoints: ['30%'] });
present(<SheetB />, { snapPoints: ['45%'] });
// dismiss() removes SheetB; SheetA becomes interactive again

Sheet content & navigation theme

Sheet bodies render in the overlay subtree, outside NavigationContainer. Do not use @react-navigation/elements (Text, Button, etc.) inside present() content — they require a navigation theme context. Use React Native components instead.

Screens inside your navigator can still use navigation UI components normally.

API reference

BottomSheetProvider

| Prop | Type | Default | Description | | -------- | ------------------------------------- | ---------------- | ------------------------------------------------------ | | mode | 'presentation' \| 'modal' \| 'push' | 'presentation' | Default host mode for all sheets | | sheet | BottomSheetOptions | — | Default options merged into every sheet | | layout | Partial<BottomSheetLayoutOptions> | — | Motion, gestures, detents, handle (read once at mount) | | theme | BottomSheetTheme | — | Default colors (sheet, letterbox, handle, scrim) |

useBottomSheet()

| Property / method | Description | | ---------------------------- | --------------------------------------------------- | | present(content, options?) | Push sheet content onto the stack; returns sheet id | | dismiss(sheetId?) | Dismiss top sheet, or a specific id | | dismissAll() | Dismiss entire stack | | isPresented | Whether any sheet is open | | presentedSheetCount | Number of sheets in the stack | | topSheetController | Imperative control for the top sheet |

Common BottomSheetOptions

| Option | Type | Description | | ----------------------------- | ------------------------------------------------------- | ------------------------------------------------ | | snapPoints | ('medium' \| 'large' \| 'full' \| number \| string)[] | Detent heights | | index | number | Initial snap index | | mode | BottomSheetMode | Override provider default | | enableDynamicSizing | boolean | Size sheet to content height | | enablePanDownToClose | boolean | Allow drag-to-dismiss | | enableContentPanningGesture | boolean | Drag sheet from scrollable content at scroll top | | dismissOnScrimPress | boolean | Tap backdrop to dismiss | | scrimColor | string | Backdrop color | | keyboardBehavior | 'interactive' \| 'fillParent' \| 'extend' | Keyboard interaction | | showHandle | boolean | Show drag handle | | onDismiss | () => void | Called after dismiss animation | | onChange | (index: number) => void | Called when snap index changes |

Full types are exported from the package entry point.

Example app

git clone https://github.com/deskbtm/react-native-bottom-sheet.git
cd bottom-sheet
pnpm install
cd example
pnpm start        # Metro — resolves module from ../src
pnpm ios          # or pnpm android / pnpm web

The example has two tabs:

| Tab | Purpose | | --------- | --------------------------------------------------------------------------------- | | Demos | Feature showcase by API category (present, BottomSheetModal, modes, backdrop) | | Debug | Manual performance verification (__DEV__ only) |

Architecture & performance

Sheet state lives in the overlay subtree (stack, backdrop, letterbox). Protected host content — screens that do not call useBottomSheet() — should not re-render on present, dismiss, or dismissAll. Host motion uses Reanimated SharedValues on the UI thread.

| Track | Focus | Tests | | ----- | --------------------------- | ------------------------------ | | A | Host render isolation | BottomSheetProvider.test.tsx | | B | UI-thread pan / host motion | gestureFluency.test.ts | | C | Scroll-to-pan handoff | sheetScrollFluency.test.ts | | D | Multi-sheet stack | sheetStackFluency.test.ts |

Details: ADR-0002 · CONTEXT.md

Development

This repo is an Expo module with a workspace example app.

pnpm test              # unit tests (watch locally)
pnpm test:ci           # single run (CI)
pnpm validate          # lint + test + build
pnpm validate:example  # example typecheck + web export
pnpm build             # compile build/ (runs automatically on publish)

From the module root:

pnpm open:ios
pnpm open:android

Native stubs: Android com.deskbtm.rn.bottomsheet, iOS BottomSheetModule. UI is TypeScript; native code exists for Expo autolinking.

Contributing

Contributions welcome. Please read:

License

MIT © nawbc