Use ref.hide() to programmatically dismiss the sheet with an animation before unmounting it.
BasicBottomSheetExample.tsx
import{ useRef, useState }from'react';import{Host,ModalBottomSheet,Button,Column,Text}from'@expo/ui/jetpack-compose';importtype{ModalBottomSheetRef}from'@expo/ui/jetpack-compose';import{ paddingAll }from'@expo/ui/jetpack-compose/modifiers';exportdefaultfunctionBasicBottomSheetExample(){const[visible, setVisible]=useState(false);const sheetRef =useRef<ModalBottomSheetRef>(null);consthideSheet=async()=>{await sheetRef.current?.hide();setVisible(false);};return(<HostmatchContents><ButtononClick={()=>setVisible(true)}><Text>Open Sheet</Text></Button>{visible &&(<ModalBottomSheetref={sheetRef}onDismissRequest={()=>setVisible(false)}><ColumnverticalArrangement={{ spacedBy:12}}modifiers={[paddingAll(24)]}><Text>Hello from bottom sheet!</Text><Text>You can add more content here.</Text><ButtononClick={hideSheet}><Text>Close</Text></Button></Column></ModalBottomSheet>)}</Host>);}
Skip partially expanded state
When skipPartiallyExpanded is set, the sheet opens directly in the fully expanded state instead of stopping at the half-height position first.
SkipPartiallyExpandedExample.tsx
import{ useRef, useState }from'react';import{Host,ModalBottomSheet,Button,Column,Text}from'@expo/ui/jetpack-compose';importtype{ModalBottomSheetRef}from'@expo/ui/jetpack-compose';import{ paddingAll, height }from'@expo/ui/jetpack-compose/modifiers';exportdefaultfunctionSkipPartiallyExpandedExample(){const[visible, setVisible]=useState(false);const sheetRef =useRef<ModalBottomSheetRef>(null);consthideSheet=async()=>{await sheetRef.current?.hide();setVisible(false);};return(<HostmatchContents><ButtononClick={()=>setVisible(true)}><Text>Open Sheet</Text></Button>{visible &&(<ModalBottomSheetref={sheetRef}onDismissRequest={()=>setVisible(false)}skipPartiallyExpanded><ColumnverticalArrangement={{ spacedBy:12}}modifiers={[paddingAll(24),height(600)]}><Text>This sheet skips the partially expanded state.</Text><Text>It opens directly in the fully expanded position.</Text><ButtononClick={hideSheet}><Text>Close</Text></Button></Column></ModalBottomSheet>)}</Host>);}
Initial fully expanded state
When initialFullyExpanded is true, the sheet opens directly in the fully expanded state on first composition while leaving the partial state reachable. Unlike skipPartiallyExpanded, the user can still drag down to the partial state. The partialExpand() method also continues to work.
InitialFullyExpandedExample.tsx
import{ useRef, useState }from'react';import{Host,ModalBottomSheet,Button,Column,Text}from'@expo/ui/jetpack-compose';importtype{ModalBottomSheetRef}from'@expo/ui/jetpack-compose';import{ paddingAll }from'@expo/ui/jetpack-compose/modifiers';exportdefaultfunctionInitialFullyExpandedExample(){const[visible, setVisible]=useState(false);const sheetRef =useRef<ModalBottomSheetRef>(null);consthideSheet=async()=>{await sheetRef.current?.hide();setVisible(false);};return(<HostmatchContents><ButtononClick={()=>setVisible(true)}><Text>Open Sheet</Text></Button>{visible &&(<ModalBottomSheetref={sheetRef}onDismissRequest={()=>setVisible(false)}initialFullyExpanded><ColumnverticalArrangement={{ spacedBy:12}}modifiers={[paddingAll(24)]}><Text>This sheet opened fully expanded.</Text><Text>You can still drag it down to the partial state.</Text><ButtononClick={()=> sheetRef.current?.partialExpand()}><Text>Collapse to partial</Text></Button><ButtononClick={hideSheet}><Text>Close</Text></Button></Column></ModalBottomSheet>)}</Host>);}
Custom colors
Use containerColor, contentColor, and scrimColor to customize the sheet's appearance.
Use RNHostView to embed interactive React Native views inside a Compose bottom sheet. This lets you mix Compose layout with RN components like Pressable and Text.
Use RNHostView without matchContents to let the RN view fill the remaining space inside the sheet. Combine with a fixed height modifier on the parent Column to control the sheet size.
Nest a scrollable React Native list such as FlatList, ScrollView, or a high-performance list like FlashList or Legend List inside the sheet with RNHostView. Set nestedScrollEnabled on the scrollable so it scrolls its own content first. Once it reaches the top edge, the remaining drag moves the sheet. Without nestedScrollEnabled the list consumes the gesture and the sheet stays put.
Combine properties, sheetGesturesEnabled to create a sheet that can only be closed programmatically.
NonDismissibleExample.tsx
import{ useRef, useState }from'react';import{Host,ModalBottomSheet,Button,Column,Text}from'@expo/ui/jetpack-compose';importtype{ModalBottomSheetRef}from'@expo/ui/jetpack-compose';import{ paddingAll }from'@expo/ui/jetpack-compose/modifiers';exportdefaultfunctionNonDismissibleExample(){const[visible, setVisible]=useState(false);const sheetRef =useRef<ModalBottomSheetRef>(null);consthideSheet=async()=>{await sheetRef.current?.hide();setVisible(false);};return(<HostmatchContents><ButtononClick={()=>setVisible(true)}><Text>Open Non-Dismissible Sheet</Text></Button>{visible &&(<ModalBottomSheetref={sheetRef}onDismissRequest={()=>setVisible(false)}sheetGesturesEnabled={false}properties={{
shouldDismissOnBackPress:false,
shouldDismissOnClickOutside:false,}}><ColumnverticalArrangement={{ spacedBy:12}}modifiers={[paddingAll(24)]}><Text>This sheet cannot be dismissed by swiping, back press, or tapping outside.</Text><Text>Only the button below will close it.</Text><ButtononClick={hideSheet}><Text>Close</Text></Button></Column></ModalBottomSheet>)}</Host>);}