Portal and host
Where a sheet draws — over the navigator by default, in place on request, or inside a native modal — and who is on top when two are open.
BottomSheet.Portal is one component with two destinations. Under a BottomSheetProvider it
teleports to the nearest host; without one, or with inline, it renders where it is written.
There is no BottomSheetModal: a modal sheet is a sheet whose portal teleports, and that is the
default.
Modal
<BottomSheet>
<BottomSheet.Trigger />
<BottomSheet.Portal>
<BottomSheet.Overlay />
<BottomSheet.Container>…</BottomSheet.Container>
</BottomSheet.Portal>
</BottomSheet>The default. Portal teleports to the app's root host — the one BottomSheetProvider renders over
its children — so the sheet draws over the navigator, the header and the tab bar, wherever in the
tree it was written. Its children unmount when the sheet has closed, so a closed sheet costs
nothing.
Teleport moves the native view and leaves the React tree where it was. A context provided around the trigger — a theme, a form, a query client, a value of your own — reads inside the sheet with nothing re-provided, and so do the sheet's own three.
const ScreenContext = createContext("not provided");
<ScreenContext.Provider value="from the trigger's screen">
<BottomSheet>
<BottomSheet.Trigger />
<BottomSheet.Portal>
<BottomSheet.Container>
<ContextReadout />
</BottomSheet.Container>
</BottomSheet.Portal>
</BottomSheet>
</ScreenContext.Provider>;Inline
<View style={{ flex: 1 }}>
<MapView />
<BottomSheet snapPoints={["25%", "60%"]} dynamicSizing={false} enablePanDownToClose={false} defaultOpen>
<BottomSheet.Portal inline>
<BottomSheet.Container>…</BottomSheet.Container>
</BottomSheet.Portal>
</BottomSheet>
</View><BottomSheet.Portal inline> renders where written, as an absolute fill of the nearest positioned
ancestor — so the parent must have a size. It never unmounts its children unless unmountOnClose
says so. This is the form for a persistent drawer, a map's result list, a sheet inside a stage that
is not the whole screen. With no Overlay the view around it stays interactive.
A Portal with no provider above it is inline whether or not it says so.
Inside a native modal
<Modal animationType="slide" visible={visible}>
<BottomSheet.Host name="modal" style={styles.modal}>
<Text>A native modal. Its own window.</Text>
<BottomSheet snapPoints={["50%"]} dynamicSizing={false}>
<BottomSheet.Trigger />
<BottomSheet.Portal>
<BottomSheet.Overlay />
<BottomSheet.Container>…</BottomSheet.Container>
</BottomSheet.Portal>
</BottomSheet>
</BottomSheet.Host>
</Modal>A native Modal, or a screen a navigator presents as one, is its own window, and a sheet teleported
to the root host draws behind it. The fix is one wrapper: <BottomSheet.Host name="modal">
around the screen's content, and every sheet written inside targets it with no hostName — the
host provides its own name as the nearest one.
With no children the host is an absolute fill instead: write <BottomSheet.Host name="modal" />
last in the screen so it is the topmost sibling, and a sheet elsewhere in that screen names it
with hostName="modal".
A host needs the provider above it, and it must not be written inside a sheet — a sheet teleported into another sheet's panel is clipped by it and moves with it. Both warn in development.
The registry
Every presented sheet is in the registry from the open that mounted its children to the settle
that unmounts them, under the host its portal chose. The registry stamps each open with the next
zIndex, so a sheet opened from inside another lands on top of it, and re-opening an open sheet
brings it forward.
<BottomSheet stackBehavior="replace">stackBehavior is what opening does to the other sheets open in the same host: push (the
default) stacks on top of them; replace closes them first. An inline sheet is its own host, so
nothing replaces it and it is always its own top.
import { useBottomSheetRegistry } from "@delacour/react-native-bottom-sheet";
function DismissAll() {
const { dismissAll } = useBottomSheetRegistry();
return <Pressable onPress={dismissAll}>…</Pressable>;
}useBottomSheetRegistry() returns dismissAll and dismiss(id), for closing sheets with no ref
in hand. It throws outside the provider; useOptionalBottomSheetRegistry() returns null.
closeOnBack (the default) subscribes to Android's BackHandler while the sheet is presented
and the top one in its host, so a back press closes the top sheet and the one under it
survives.
Provider
import { BottomSheetProvider } from "@delacour/react-native-bottom-sheet";Mounted once by the app, inside its gesture root and keyboard provider, around its navigator — see
Installation. It is also named on the compound as
BottomSheet.Provider. Nothing else is provided by this package.
API Reference
Portal
Prop
Type
Host
Prop
Type
Provider
Prop
Type
useBottomSheetRegistry
Prop
Type
Scrollables
A ScrollView, FlatList or SectionList as the sheet's body, the factory that makes one from any animated list, and the rule for which of the two a finger moves.
Detached
A floating card — inset from the sides, resting above the safe area, every corner rounded — that closes as one rigid body.