This documentation is available as Markdown for AI agents and LLMs. See the full Markdown index or append .md to any documentation URL.
ModalBottomSheet
一个 Jetpack Compose ModalBottomSheet 组件,从屏幕底部渲染内容。
信息 有关跨平台使用,请参阅通用
BottomSheet—— 它会根据平台呈现相应的原生组件。
Expo UI ModalBottomSheet 与官方的 Jetpack Compose Bottom Sheet API 匹配,并在从底部滑出的模态面板中显示内容。
🌐 Expo UI ModalBottomSheet matches the official Jetpack Compose Bottom Sheet API and displays content in a modal sheet that slides up from the bottom.

安装
🌐 Installation
- npx expo install @expo/uiIf you are installing this in an existing React Native app, make sure to install expo in your project.
用法
🌐 Usage
基本底部面板
🌐 Basic bottom sheet
使用 ref.hide() 在卸载前通过动画以编程方式关闭该面板。
🌐 Use ref.hide() to programmatically dismiss the sheet with an animation before unmounting it.
import { useRef, useState } from 'react'; import { Host, ModalBottomSheet, Button, Column, Text } from '@expo/ui/jetpack-compose'; import type { ModalBottomSheetRef } from '@expo/ui/jetpack-compose'; import { paddingAll } from '@expo/ui/jetpack-compose/modifiers'; export default function BasicBottomSheetExample() { const [visible, setVisible] = useState(false); const sheetRef = useRef<ModalBottomSheetRef>(null); const hideSheet = async () => { await sheetRef.current?.hide(); setVisible(false); }; return ( <Host matchContents> <Button onClick={() => setVisible(true)}> <Text>Open Sheet</Text> </Button> {visible && ( <ModalBottomSheet ref={sheetRef} onDismissRequest={() => setVisible(false)}> <Column verticalArrangement={{ spacedBy: 12 }} modifiers={[paddingAll(24)]}> <Text>Hello from bottom sheet!</Text> <Text>You can add more content here.</Text> <Button onClick={hideSheet}> <Text>Close</Text> </Button> </Column> </ModalBottomSheet> )} </Host> ); }
跳过部分展开状态
🌐 Skip partially expanded state
当设置 skipPartiallyExpanded 时,表单会直接以完全展开的状态打开,而不是先停在半高位置。
🌐 When skipPartiallyExpanded is set, the sheet opens directly in the fully expanded state instead of stopping at the half-height position first.
import { useRef, useState } from 'react'; import { Host, ModalBottomSheet, Button, Column, Text } from '@expo/ui/jetpack-compose'; import type { ModalBottomSheetRef } from '@expo/ui/jetpack-compose'; import { paddingAll, height } from '@expo/ui/jetpack-compose/modifiers'; export default function SkipPartiallyExpandedExample() { const [visible, setVisible] = useState(false); const sheetRef = useRef<ModalBottomSheetRef>(null); const hideSheet = async () => { await sheetRef.current?.hide(); setVisible(false); }; return ( <Host matchContents> <Button onClick={() => setVisible(true)}> <Text>Open Sheet</Text> </Button> {visible && ( <ModalBottomSheet ref={sheetRef} onDismissRequest={() => setVisible(false)} skipPartiallyExpanded> <Column verticalArrangement={{ 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> <Button onClick={hideSheet}> <Text>Close</Text> </Button> </Column> </ModalBottomSheet> )} </Host> ); }
初始完全展开状态
🌐 Initial fully expanded state
当 initialFullyExpanded 为 true 时,表单在首次组合时会直接以完全展开的状态打开,同时仍然可以访问部分状态。与 skipPartiallyExpanded 不同,用户仍然可以向下拖动以进入部分状态。partialExpand() 方法也继续有效。
🌐 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.
import { useRef, useState } from 'react'; import { Host, ModalBottomSheet, Button, Column, Text } from '@expo/ui/jetpack-compose'; import type { ModalBottomSheetRef } from '@expo/ui/jetpack-compose'; import { paddingAll } from '@expo/ui/jetpack-compose/modifiers'; export default function InitialFullyExpandedExample() { const [visible, setVisible] = useState(false); const sheetRef = useRef<ModalBottomSheetRef>(null); const hideSheet = async () => { await sheetRef.current?.hide(); setVisible(false); }; return ( <Host matchContents> <Button onClick={() => setVisible(true)}> <Text>Open Sheet</Text> </Button> {visible && ( <ModalBottomSheet ref={sheetRef} onDismissRequest={() => setVisible(false)} initialFullyExpanded> <Column verticalArrangement={{ spacedBy: 12 }} modifiers={[paddingAll(24)]}> <Text>This sheet opened fully expanded.</Text> <Text>You can still drag it down to the partial state.</Text> <Button onClick={() => sheetRef.current?.partialExpand()}> <Text>Collapse to partial</Text> </Button> <Button onClick={hideSheet}> <Text>Close</Text> </Button> </Column> </ModalBottomSheet> )} </Host> ); }
自定义颜色
🌐 Custom colors
使用 containerColor、contentColor 和 scrimColor 来自定义工作表的外观。
🌐 Use containerColor, contentColor, and scrimColor to customize the sheet's appearance.
import { useRef, useState } from 'react'; import { Host, ModalBottomSheet, Button, Column, Text } from '@expo/ui/jetpack-compose'; import type { ModalBottomSheetRef } from '@expo/ui/jetpack-compose'; import { paddingAll } from '@expo/ui/jetpack-compose/modifiers'; export default function CustomColorsExample() { const [visible, setVisible] = useState(false); const sheetRef = useRef<ModalBottomSheetRef>(null); const hideSheet = async () => { await sheetRef.current?.hide(); setVisible(false); }; return ( <Host matchContents> <Button onClick={() => setVisible(true)}> <Text>Open Colored Sheet</Text> </Button> {visible && ( <ModalBottomSheet ref={sheetRef} onDismissRequest={() => setVisible(false)} containerColor="#1a1a2e" contentColor="#e0e0e0" scrimColor="#806200EE"> <Column verticalArrangement={{ spacedBy: 12 }} modifiers={[paddingAll(24)]}> <Text>Custom styled bottom sheet.</Text> <Text>Dark container with a purple scrim overlay.</Text> <Button onClick={hideSheet}> <Text>Close</Text> </Button> </Column> </ModalBottomSheet> )} </Host> ); }
自定义拖动句柄
🌐 Custom drag handle
使用 ModalBottomSheet.DragHandle 插槽提供自定义拖动句柄,或设置 showDragHandle={false} 完全隐藏它。
🌐 Use ModalBottomSheet.DragHandle slot to provide a custom drag handle, or set showDragHandle={false} to hide it entirely.
import { useRef, useState } from 'react'; import { Host, ModalBottomSheet, Button, Column, Box, Text } from '@expo/ui/jetpack-compose'; import type { ModalBottomSheetRef } from '@expo/ui/jetpack-compose'; import { background, clip, fillMaxWidth, height, padding, Shapes, width, } from '@expo/ui/jetpack-compose/modifiers'; export default function CustomDragHandleExample() { const [visible, setVisible] = useState(false); const sheetRef = useRef<ModalBottomSheetRef>(null); const hideSheet = async () => { await sheetRef.current?.hide(); setVisible(false); }; return ( <Host matchContents> <Button onClick={() => setVisible(true)}> <Text>Open Custom Handle Sheet</Text> </Button> {visible && ( <ModalBottomSheet ref={sheetRef} onDismissRequest={() => setVisible(false)}> <ModalBottomSheet.DragHandle> <Column horizontalAlignment="center" modifiers={[fillMaxWidth(), padding(0, 12, 0, 8)]}> <Box modifiers={[width(60), height(6), clip(Shapes.Circle), background('#6200EE')]} /> </Column> </ModalBottomSheet.DragHandle> <Column verticalArrangement={{ spacedBy: 12 }} modifiers={[padding(16, 16, 16, 16)]}> <Button onClick={hideSheet}> <Text>Close</Text> </Button> </Column> </ModalBottomSheet> )} </Host> ); }
底部弹出层中的 React Native 内容
🌐 React Native content inside a bottom sheet
使用 RNHostView 在 Compose 底部弹出窗中嵌入交互式的 React Native 视图。这让你可以将 Compose 布局与 RN 组件如 Pressable 和 Text 混合使用。
🌐 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.
import { useRef, useState } from 'react'; import { Host, ModalBottomSheet, Button, Column, RNHostView, Text } from '@expo/ui/jetpack-compose'; import type { ModalBottomSheetRef } from '@expo/ui/jetpack-compose'; import { padding } from '@expo/ui/jetpack-compose/modifiers'; import { Pressable, Text as RNText, View } from 'react-native'; export default function RNContentBottomSheetExample() { const [visible, setVisible] = useState(false); const sheetRef = useRef<ModalBottomSheetRef>(null); const hideSheet = async () => { await sheetRef.current?.hide(); setVisible(false); }; return ( <Host matchContents> <Button onClick={() => setVisible(true)}> <Text>Open RN Content Sheet</Text> </Button> {visible && ( <ModalBottomSheet ref={sheetRef} onDismissRequest={() => setVisible(false)} skipPartiallyExpanded={false}> <Column verticalArrangement={{ spacedBy: 16 }} modifiers={[padding(16, 16, 16, 16)]}> <Text>Mixing Compose + RN in a Bottom Sheet</Text> <RNHostView> <View> <RNText style={{ fontSize: 18, fontWeight: 'bold', marginBottom: 8 }}> React Native Content </RNText> <Pressable style={{ backgroundColor: '#007AFF', padding: 12, borderRadius: 8, alignItems: 'center', }} onPress={hideSheet}> <RNText style={{ color: 'white', fontWeight: '600' }}>Close</RNText> </Pressable> </View> </RNHostView> </Column> </ModalBottomSheet> )} </Host> ); }
带有 flex 的 React Native 内容
🌐 React Native content with flex
在不使用 matchContents 的情况下使用 RNHostView,让 RN 视图填充表单内部的剩余空间。与在父 Column 上使用固定的 height 修饰符结合,以控制表单的大小。
🌐 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.
import { useRef, useState } from 'react'; import { Host, ModalBottomSheet, Button, Column, RNHostView, Text } from '@expo/ui/jetpack-compose'; import type { ModalBottomSheetRef } from '@expo/ui/jetpack-compose'; import { height, padding } from '@expo/ui/jetpack-compose/modifiers'; import { Text as RNText, View } from 'react-native'; export default function FlexRNContentExample() { const [visible, setVisible] = useState(false); const sheetRef = useRef<ModalBottomSheetRef>(null); const hideSheet = async () => { await sheetRef.current?.hide(); setVisible(false); }; return ( <Host matchContents> <Button onClick={() => setVisible(true)}> <Text>Open Flex Content Sheet</Text> </Button> {visible && ( <ModalBottomSheet ref={sheetRef} onDismissRequest={() => setVisible(false)} skipPartiallyExpanded> <Column modifiers={[height(400), padding(16, 16, 16, 16)]}> <Text>RN View with flex: 1</Text> <RNHostView> <View style={{ flex: 1, backgroundColor: '#9B59B6', borderRadius: 10 }}> <RNText style={{ color: 'white', fontSize: 18, fontWeight: 'bold', padding: 16, }}> React Native Content (flex: 1) </RNText> </View> </RNHostView> </Column> </ModalBottomSheet> )} </Host> ); }
可滚动的 React Native 内容
🌐 Scrollable React Native content
在使用 RNHostView 的面板中嵌套一个可滚动的 React Native 列表,例如 FlatList、ScrollView,或者高性能列表如 FlashList 或 Legend List。在可滚动组件上设置 nestedScrollEnabled,使其先滚动自身内容。一旦滚动到顶部边缘,剩余的拖动将移动面板。如果没有 nestedScrollEnabled,列表会消耗手势,而面板保持不动。
🌐 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.
import { useState } from 'react'; import { Host, ModalBottomSheet, Button, Column, RNHostView, Text } from '@expo/ui/jetpack-compose'; import { fillMaxHeight, padding } from '@expo/ui/jetpack-compose/modifiers'; import { FlatList, Text as RNText } from 'react-native'; const DATA = Array.from({ length: 50 }, (_, i) => `Item ${i + 1}`); export default function ScrollableContentBottomSheetExample() { const [visible, setVisible] = useState(false); return ( <Host matchContents> <Button onClick={() => setVisible(true)}> <Text>Open Scrollable Sheet</Text> </Button> {visible && ( <ModalBottomSheet onDismissRequest={() => setVisible(false)}> <Column modifiers={[fillMaxHeight(), padding(16, 16, 16, 16)]}> <RNHostView> <FlatList nestedScrollEnabled style={{ flex: 1 }} data={DATA} keyExtractor={item => item} renderItem={({ item }) => <RNText style={{ paddingVertical: 16 }}>{item}</RNText>} /> </RNHostView> </Column> </ModalBottomSheet> )} </Host> ); }
不可取消的面板
🌐 Non-dismissible sheet
将 properties、sheetGesturesEnabled 结合起来创建一个只能通过编程方式关闭的表单。
🌐 Combine properties, sheetGesturesEnabled to create a sheet that can only be closed programmatically.
import { useRef, useState } from 'react'; import { Host, ModalBottomSheet, Button, Column, Text } from '@expo/ui/jetpack-compose'; import type { ModalBottomSheetRef } from '@expo/ui/jetpack-compose'; import { paddingAll } from '@expo/ui/jetpack-compose/modifiers'; export default function NonDismissibleExample() { const [visible, setVisible] = useState(false); const sheetRef = useRef<ModalBottomSheetRef>(null); const hideSheet = async () => { await sheetRef.current?.hide(); setVisible(false); }; return ( <Host matchContents> <Button onClick={() => setVisible(true)}> <Text>Open Non-Dismissible Sheet</Text> </Button> {visible && ( <ModalBottomSheet ref={sheetRef} onDismissRequest={() => setVisible(false)} sheetGesturesEnabled={false} properties={{ shouldDismissOnBackPress: false, shouldDismissOnClickOutside: false, }}> <Column verticalArrangement={{ 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> <Button onClick={hideSheet}> <Text>Close</Text> </Button> </Column> </ModalBottomSheet> )} </Host> ); }
应用接口
🌐 API
import { ModalBottomSheet } from '@expo/ui/jetpack-compose';
Constants
Props
ReactNodeThe children of the ModalBottomSheet component.
Can include a ModalBottomSheet.DragHandle slot for a custom drag handle.
boolean • Default: falseOpens the sheet fully expanded on first composition. Ignored when skipPartiallyExpanded is true.
() => voidCallback function that is called when the user dismisses the bottom sheet (via swipe, back press, or tapping outside the scrim).
Ref<ModalBottomSheetRef>Can be used to imperatively hide the bottom sheet with an animation.
boolean • Default: trueWhether gestures (swipe to dismiss) are enabled on the bottom sheet.
boolean • Default: trueWhether to show the default drag handle at the top of the bottom sheet.
Ignored if a custom ModalBottomSheet.DragHandle slot is provided.
Types
| Property | Type | Description |
|---|---|---|
| shouldDismissOnBackPress(optional) | boolean | Whether the bottom sheet can be dismissed by pressing the back button. Default: true |
| shouldDismissOnClickOutside(optional) | boolean | Whether the bottom sheet can be dismissed by clicking outside (on the scrim). Default: true |
| Property | Type | Description |
|---|---|---|
| expand | () => Promise<void> | Programmatically expands the bottom sheet to full height with an animation. |
| hide | () => Promise<void> | Programmatically hides the bottom sheet with an animation. The returned promise resolves after the dismiss animation completes. |
| partialExpand | () => Promise<void> | Programmatically collapses the bottom sheet to partially expanded (~50%) state.
Only works when |