Composition
The anatomy of a sheet, what each part does, and every prop each one takes.
A sheet is a root and the parts placed inside it. The root does all of the arithmetic — the
snap points, the keyboard lift, where the footer sits — and publishes it through three contexts. A part
reads those and renders one thing, and every part accepts style and ref.
import { BottomSheet } from "@delacour/react-native-bottom-sheet";
<BottomSheet>
<BottomSheet.Trigger />
<BottomSheet.Portal>
<BottomSheet.Overlay />
<BottomSheet.Container>
<BottomSheet.Background />
<BottomSheet.Handle />
<BottomSheet.Content>
<BottomSheet.Title />
<BottomSheet.Description />
<BottomSheet.TextInput />
<BottomSheet.Close />
</BottomSheet.Content>
<BottomSheet.Footer />
</BottomSheet.Container>
</BottomSheet.Portal>
</BottomSheet>;The render tree
BottomSheet the root — state, geometry, animation, the pans, the ref; renders nothing
Trigger anywhere in the app's tree; opens the sheet
Portal the frame — teleported to the nearest host, or in place with `inline`
Overlay the scrim, written BEFORE the panel so hit-testing gives the panel its taps
Container the panel — the one view that moves
Background an absolute fill under everything; where the surface colour and radius go
Handle the grabber row; a pan detector and the sheet's one adjustable element
Content the static body; a pan detector; clamped to the area above the footer
Title labels the panel for assistive technology
Description supporting copy
TextInput a field the sheet owns the keyboard for
Close dismisses
Footer sticky over the body, riding the keyboard; or a plain view that scrollsThe same parts, as they are laid out on the screen:
+-- Portal: the frame ---------------------+
| |
| Overlay: fills the frame, behind |
| |
| +-- Container: the panel, the one -----+ |
| | view that moves | |
| | +----------------------------------+ | |
| | | Handle ==== | | |
| | +----------------------------------+ | |
| | | Content | | | Background fills the panel
| | | Title | | | under all three
| | | Description | | |
| | +----------------------------------+ | |
| | | Footer: absolute, over the body | | |
| | +----------------------------------+ | |
+-+--------------------------------------+-+Exactly one body: Content, or one of the scrollables, or
Steps. Footer is written beside the body, not inside it, so it
can stay put while the body scrolls under it.
Container is the moving panel and Content is the static body. That is the naming the skinned
library uses too, so a sheet written against one reads against the other.
The overlay is written first
Native hit-testing walks siblings back to front, so a touch on the panel never reaches the overlay
and a touch beside it does. The overlay is a React Native Pressable, never a Gesture Handler tap:
a full-screen tap gesture behind the sheet wins a touch on a TextInput, which has no gesture of
its own, and the field never focuses. Omit Overlay and the sheet has no scrim, and the app behind
it stays interactive.
Context reaches every part
The three contexts are provided once, by the root. A teleported portal moves the native view and leaves the React tree where it was, so they reach every part without being re-provided — and so does every context your app provides around the trigger: a theme, a form, a query client.
import { useBottomSheet, useBottomSheetAnimated } from "@delacour/react-native-bottom-sheet";
import Animated, { useAnimatedStyle } from "react-native-reanimated";
function Chevron() {
const { index, snapToIndex } = useBottomSheet();
const { animatedIndex } = useBottomSheetAnimated();
const style = useAnimatedStyle(() => ({ transform: [{ rotate: `${animatedIndex.value * 180}deg` }] }));
return <Animated.View onTouchEnd={() => snapToIndex(index === 0 ? 1 : 0)} style={style} />;
}| Hook | Returns | Thread |
|---|---|---|
useBottomSheet() | isOpen, setOpen, index, snapPointCount, and every ref method | JS — React state, updated on settle |
useBottomSheetAnimated() | animatedIndex, animatedPosition, animatedHeight, containerHeight, handleHeight, contentHeight, footerHeight, keyboardLift, snapPoints, closedHeight | UI — shared values, for useAnimatedStyle |
useBottomSheetInternal() | what the parts share, for a part written outside the package |
Each throws outside a <BottomSheet>; the useOptional… form returns null instead.
The ref
const sheet = useRef<BottomSheetRef>(null);
sheet.current?.snapToIndex(1);
sheet.current?.snapToPosition("50%");
sheet.current?.expand();
sheet.current?.collapse();
sheet.current?.open();
sheet.current?.close();
sheet.current?.dismiss(); // close, under the name the library this replaces used
sheet.current?.forceClose(); // no animationEvery method is an intent: none animates from the JS thread, and each is a no-op when it makes no
sense — close on a closed sheet, snapToIndex(4) with two snap points. snapToPosition clamps to
the available height and never adds a snap point. open and close are the same state a controlled
isOpen drives.
API Reference
BottomSheet
Prop
Type
Trigger
Opens the sheet. A React Native Pressable around its children, or with asChild the child
itself with onPress donated to it.
Prop
Type
Portal
The frame the sheet measures itself against. Teleported to the nearest host under a provider; in
place with inline or without one. See Portal and host.
Prop
Type
Overlay
The scrim. Its opacity follows the keyboard-free index on the UI thread, so a keyboard lift never dims the app and a higher snap point is no darker.
Prop
Type
Container
The panel — the one view that moves. accessibilityViewIsModal while open under an overlay, its
descendants hidden from assistive technology while closed, labelled by the Title.
Prop
Type
Background
An absolute fill under everything in the panel, pointerEvents: none. Where the surface colour and
the corner radii go. Exactly the sheet's height tall when detached, so a floating card's bottom
corners are real.
Prop
Type
Handle
The grabber's row: a pan detector, and the sheet's one adjustable accessibility element —
increment and decrement move a snap point, escape closes, and its value reads Snap point i of n. Put the
pill inside it.
Prop
Type
Content
The static body: a pan detector, clamped to the area above the footer and the keyboard, with an inner box that measures the dynamic snap point.
Prop
Type
Footer
Sticky over the body by default, riding the keyboard by transform. See Footer.
Prop
Type
TextInput
React Native's TextInput with useBottomSheetTextInput() already spread on it. See
Keyboard.
Prop
Type
Close
Dismisses. A Pressable, or with asChild the child with onPress donated.
Prop
Type
Title and Description
A Text each. Title publishes the nativeID the panel is labelled by; Description publishes
one of its own. Both take asChild to hand those to a text of yours.
Prop
Type
Host and Provider
BottomSheet.Host and BottomSheet.Provider are named on the compound from src/portal; they
are documented on Portal and host. ScrollView, FlatList
and SectionList are on Scrollables; Steps and Step on
Steps.