BottomSheet
Compound bottom sheet with snap points, gestures and scrollable content.
Installation
npx axiom add bottom-sheetpnpm dlx axiom add bottom-sheetyarn dlx axiom add bottom-sheetbun x axiom add bottom-sheetAlso copies: portal, overlay, tappable, slot, title, icon-button.
Requires the close icon in your icon registry.
The sheet follows the keyboard with react-native-keyboard-controller. It’s a native module: rebuild your development build after installing it, and wrap your app in its KeyboardProvider:
import { KeyboardProvider } from "react-native-keyboard-controller";
<GestureHandlerRootView style={{ flex: 1 }}>
<KeyboardProvider>
<PortalProvider>{/* … */}</PortalProvider>
</KeyboardProvider>
</GestureHandlerRootView>;Installs: react-native-reanimated, react-native-worklets, react-native-gesture-handler, react-native-safe-area-context.
The sheet renders in the root PortalHost, and its gestures need GestureHandlerRootView above it. Wrap your root layout once:
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { PortalHost, PortalProvider } from "@/components/core/portal";
export default function RootLayout() {
return (
<GestureHandlerRootView style={{ flex: 1 }}>
<PortalProvider>
<Stack />
<PortalHost />
</PortalProvider>
</GestureHandlerRootView>
);
}Usage
import { BottomSheet } from "@/components/ui/bottom-sheet";
<BottomSheet.Root>
<BottomSheet.Trigger asChild>
<Button>Share</Button>
</BottomSheet.Trigger>
<BottomSheet.Content snapPoints={["50%", "90%"]}>
<BottomSheet.Handle />
<BottomSheet.Header title="Share" />
{/* content */}
</BottomSheet.Content>
</BottomSheet.Root>;Props
BottomSheet.Root
| Prop | Type | Default | Description |
|---|---|---|---|
open | boolean | — | Controlled visibility. |
defaultOpen | boolean | false | Initial visibility when the sheet manages its own state. |
onOpenChange | (open: boolean) => void | — | Called when the sheet opens or closes, including by swipe, backdrop press or a back action (Android back button, back gesture with a navigator). |
children | ReactNode | — | Trigger and Content. |
BottomSheet.Trigger
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | boolean | false | Opens the sheet from the child element instead of rendering a pressable. |
children | ReactNode | — | The element that opens the sheet. |
BottomSheet.Content
| Prop | Type | Default | Description |
|---|---|---|---|
snapPoints | Array<number | string> | ['content'] | Heights the sheet rests at, from lowest to highest: points (320), a percentage of the screen ('50%'), or 'content' to size it to its children. |
index | number | 0 | Controlled snap point. |
onIndexChange | (index: number) => void | — | Called when the sheet settles on another snap point. |
overlay | boolean | true | Dims the screen behind the sheet through Overlay. The dimming follows the sheet position. |
dismissible | boolean | true | Closes on swipe down, backdrop press and back actions. false for a step the user must complete. |
detached | boolean | false | Floats above the bottom edge with all corners rounded, for short confirmations. |
keyboardBehavior | 'interactive' | 'extend' | 'none' | 'interactive' | How the sheet reacts when an input inside it opens the keyboard. interactive rises with the keyboard frame by frame, without going past the top safe area. extend also moves to the highest snap point. none leaves the sheet in place. Closing the sheet dismisses the keyboard. |
footer | ReactNode | — | Floating actions pinned above the bottom safe area, visible at every snap point. |
onDismiss | () => void | — | Called after the close animation ends. |
style | StyleProp<ViewStyle> | — | Extra styles for the sheet surface. |
BottomSheet.Handle, BottomSheet.Header, BottomSheet.ScrollView
| Part | Props | Description |
|---|---|---|
Handle | style | The grab bar. Drag it to resize. Announced as “Resize sheet” with up and down actions. |
Header | title, leading, trailing, closeButton, style | Title row. closeButton adds an IconButton that closes the sheet, with the close icon of your registry. |
ScrollView / FlatList | same as React Native | Scrollable content that hands the gesture back to the sheet when scrolled to the top. |
Use cases
Actions on an item
snapPoints={['content']}: the sheet takes the height it needs and no more.
<BottomSheet.Content detached>
<BottomSheet.Handle />
<FileSummary file={file} />
<OptionItem label="Share" trailing={<Icon name="share" />} onPress={share} />
<OptionItem label="Delete" destructive onPress={confirmDelete} />
</BottomSheet.Content>Short form without leaving the screen
The sheet rises above the keyboard; the main button lives in footer so it stays reachable.
<BottomSheet.Content snapPoints={["content"]} keyboardBehavior="extend">
<BottomSheet.Header
leading={
<Button variant="ghost" onPress={close}>
Cancel
</Button>
}
title="New list"
trailing={
<Button variant="ghost" disabled={!name} onPress={create}>
Create
</Button>
}
/>
<Input autoFocus value={name} onChangeText={setName} />
</BottomSheet.Content>Map with a draggable list
Several snap points over content that stays interactive. The list scrolls once the sheet is fully open.
<BottomSheet.Root open>
<BottomSheet.Content
snapPoints={[120, "45%", "90%"]}
overlay={false}
dismissible={false}
>
<BottomSheet.Handle />
<SearchBar />
<BottomSheet.FlatList data={places} renderItem={renderPlace} />
</BottomSheet.Content>
</BottomSheet.Root>When to use it
Use a sheet when the user needs to act without losing the current screen: pick an option, fill a short form, confirm details. See Mobile design guidelines.