---
modificationDate: September 15, 2026
title: BottomSheet
description: A modal sheet that slides up from the bottom of the screen.
sourceCodeUrl: 'https://github.com/expo/expo/tree/sdk-58/packages/expo-ui'
packageName: '@expo/ui'
platforms: ['android', 'ios', 'web', 'expo-go']
---

This documentation is available as Markdown for AI agents and LLMs. See the [full Markdown index](/llms.txt) or append .md to any documentation URL.

# BottomSheet

A modal sheet that slides up from the bottom of the screen.

<AgentInstructions>

## Submitting Feedback

If you encounter errors, misleading or outdated information, report it so Expo can be improved:

Preferred command:
npx --yes submit-expo-feedback@latest --category docs --subject "/versions/v58.0.0/sdk/ui/universal/bottomsheet/" "<actionable feedback>"

Direct HTTP fallback:
curl -X POST https://api.expo.dev/v2/feedback/docs-send -H 'Content-Type: application/json' -d '{"url":"/versions/v58.0.0/sdk/ui/universal/bottomsheet/","feedback":"🤖 Agent feedback for docs: <specific, actionable description> (<model>, <harness>)"}'

Only submit when you have something specific and actionable to report. Try to give the most context.

## Navigation

When answering a related or follow-up question, fetch the relevant page below as Markdown (.md) instead of guessing; use llms.txt for the full map.

You are here: Reference (v58.0.0) > Expo UI > Universal
Pages in this section:
- [Overview](https://docs.expo.dev/versions/v58.0.0/sdk/ui/universal.md)
- [BottomSheet](https://docs.expo.dev/versions/v58.0.0/sdk/ui/universal/bottomsheet.md) (this page)
- [Button](https://docs.expo.dev/versions/v58.0.0/sdk/ui/universal/button.md)
- [Checkbox](https://docs.expo.dev/versions/v58.0.0/sdk/ui/universal/checkbox.md)
- [Collapsible](https://docs.expo.dev/versions/v58.0.0/sdk/ui/universal/collapsible.md)
- [Column](https://docs.expo.dev/versions/v58.0.0/sdk/ui/universal/column.md)
- [FieldGroup](https://docs.expo.dev/versions/v58.0.0/sdk/ui/universal/fieldgroup.md)
- [Host](https://docs.expo.dev/versions/v58.0.0/sdk/ui/universal/host.md)
- [Icon](https://docs.expo.dev/versions/v58.0.0/sdk/ui/universal/icon.md)
- [List](https://docs.expo.dev/versions/v58.0.0/sdk/ui/universal/list.md)
- [Picker](https://docs.expo.dev/versions/v58.0.0/sdk/ui/universal/picker.md)
- [RNHostView](https://docs.expo.dev/versions/v58.0.0/sdk/ui/universal/rnhostview.md)
- [Row](https://docs.expo.dev/versions/v58.0.0/sdk/ui/universal/row.md)
- [ScrollView](https://docs.expo.dev/versions/v58.0.0/sdk/ui/universal/scrollview.md)
- [Slider](https://docs.expo.dev/versions/v58.0.0/sdk/ui/universal/slider.md)
- [Spacer](https://docs.expo.dev/versions/v58.0.0/sdk/ui/universal/spacer.md)
- [Switch](https://docs.expo.dev/versions/v58.0.0/sdk/ui/universal/switch.md)
- [Text](https://docs.expo.dev/versions/v58.0.0/sdk/ui/universal/text.md)
- [TextInput](https://docs.expo.dev/versions/v58.0.0/sdk/ui/universal/textinput.md)
Full documentation tree: [llms.txt](https://docs.expo.dev/llms.txt)

</AgentInstructions>
Android, iOS, Web, Included in Expo Go

A modal sheet that slides up from the bottom of the screen. The sheet's visibility is controlled — toggle [`isPresented`](/versions/v58.0.0/sdk/ui/universal/bottomsheet.md#ispresented) from React state and dismiss it from [`onDismiss`](/versions/v58.0.0/sdk/ui/universal/bottomsheet.md#ondismiss) (called when the user swipes down or taps the overlay).

> On iOS, to show a bottom sheet on top of another, nest the second `BottomSheet` inside the first sheet's content rather than beside it. This is a limitation of the underlying SwiftUI [`sheet`](https://developer.apple.com/documentation/swiftui/view/sheet\(ispresented:ondismiss:content:\)) modifier. See [how to present multiple sheets](https://www.hackingwithswift.com/quick-start/swiftui/how-to-present-multiple-sheets) for more information.

Image: Modal bottom sheet with title, description, and action buttons

## Installation

```sh
# npm
npx expo install @expo/ui

# yarn
yarn expo install @expo/ui

# pnpm
pnpm expo install @expo/ui

# bun
bun expo install @expo/ui
```

If you are installing this in an [existing React Native app](/bare/overview.md), make sure to [install `expo`](/bare/installing-expo-modules.md) in your project.

## Usage

### Basic bottom sheet

```tsx BottomSheetExample.tsx
import { useState } from 'react';
import { useColorScheme } from 'react-native';
import { Host, Column, Button, BottomSheet, Text } from '@expo/ui';

export default function BottomSheetExample() {
  const [isPresented, setIsPresented] = useState(false);
  const colorScheme = useColorScheme();
  const ink = { color: colorScheme === 'dark' ? '#FFFFFF' : '#000000' };

  return (
    <>
      <Host matchContents>
        <Button label="Open sheet" onPress={() => setIsPresented(true)} />
      </Host>
      <BottomSheet isPresented={isPresented} onDismiss={() => setIsPresented(false)}>
        <Column spacing={12}>
          <Text textStyle={{ ...ink, fontSize: 18, fontWeight: '700' }}>Sheet contents</Text>
          <Text textStyle={ink}>Drag down or tap the overlay to dismiss.</Text>
          <Button label="Close" onPress={() => setIsPresented(false)} />
        </Column>
      </BottomSheet>
    </>
  );
}
```

### Hiding the drag indicator

Pass [`showDragIndicator={false}`](/versions/v58.0.0/sdk/ui/universal/bottomsheet.md#showdragindicator) for sheets without a handle.

```tsx BottomSheetNoIndicatorExample.tsx
import { useState } from 'react';
import { useColorScheme } from 'react-native';
import { Host, Button, BottomSheet, Text } from '@expo/ui';

export default function BottomSheetNoIndicatorExample() {
  const [isPresented, setIsPresented] = useState(false);
  const colorScheme = useColorScheme();
  const ink = { color: colorScheme === 'dark' ? '#FFFFFF' : '#000000' };

  return (
    <>
      <Host matchContents>
        <Button label="Open" onPress={() => setIsPresented(true)} />
      </Host>
      <BottomSheet
        isPresented={isPresented}
        onDismiss={() => setIsPresented(false)}
        showDragIndicator={false}>
        <Text textStyle={ink}>No drag handle.</Text>
      </BottomSheet>
    </>
  );
}
```

### Content padding

The sheet insets its content by default. Pass [`contentPadding`](/versions/v58.0.0/sdk/ui/universal/bottomsheet.md#contentpadding) to change that inset — `0` lets a row, image, or divider reach the sheet's edges.

```tsx BottomSheetContentPaddingExample.tsx
import { useState } from 'react';
import { Host, BottomSheet, Button, Column, Text } from '@expo/ui';

export default function BottomSheetContentPaddingExample() {
  const [isPresented, setIsPresented] = useState(false);

  return (
    <>
      <Host matchContents>
        <Button label="Open" onPress={() => setIsPresented(true)} />
      </Host>
      <BottomSheet
        isPresented={isPresented}
        onDismiss={() => setIsPresented(false)}
        contentPadding={0}>
        <Column>
          <Column style={{ backgroundColor: '#0a84ff', padding: 16 }}>
            <Text textStyle={{ color: '#FFFFFF' }}>This banner reaches the sheet's edge.</Text>
          </Column>
          <Button label="Close" onPress={() => setIsPresented(false)} />
        </Column>
      </BottomSheet>
    </>
  );
}
```

### Snap points

Pass [`snapPoints`](/versions/v58.0.0/sdk/ui/universal/bottomsheet.md#snappoints) to let the user drag the sheet between multiple resting heights. You can use the semantic values `'half'` and `'full'` for cross-platform parity. The `{ fraction }` and `{ height }` forms are honored precisely on iOS and web.

When sheet content can be taller than the smallest snap point, wrap it in a `ScrollView` so the overflow scrolls correctly.

```tsx BottomSheetSnapPointsExample.tsx
import { useState } from 'react';
import { useColorScheme } from 'react-native';
import { Host, BottomSheet, Button, Column, ScrollView, Text } from '@expo/ui';

export default function BottomSheetSnapPointsExample() {
  const [isPresented, setIsPresented] = useState(false);
  const colorScheme = useColorScheme();
  const ink = { color: colorScheme === 'dark' ? '#FFFFFF' : '#000000' };

  return (
    <>
      <Host matchContents>
        <Button label="Open" onPress={() => setIsPresented(true)} />
      </Host>
      <BottomSheet
        isPresented={isPresented}
        onDismiss={() => setIsPresented(false)}
        snapPoints={['half', 'full']}>
        <ScrollView>
          <Column spacing={12}>
            <Text textStyle={{ ...ink, fontSize: 20, fontWeight: '700' }}>Half / full sheet</Text>
            <Text textStyle={ink}>Drag the sheet between half and full screen height.</Text>
          </Column>
        </ScrollView>
      </BottomSheet>
    </>
  );
}
```

> On Android, `{ fraction }` and `{ height }` snap to the nearest of `'half'` / `'full'` — the underlying `ModalBottomSheet` only supports two resting states. The partial state is only visible when content is tall enough to exceed Material's partial threshold; give the content an explicit height or fill the available space if you need the half state on short content.

### Scrollable React Native content

The bottom sheet supports a React Native list such as `FlatList` (or a high-performance list like [FlashList](https://shopify.github.io/flash-list/) or [Legend List](https://github.com/LegendApp/legend-list)) as a child when wrapped in [`RNHostView`](/versions/v58.0.0/sdk/ui/universal/rnhostview.md). [`snapPoints`](/versions/v58.0.0/sdk/ui/universal/bottomsheet.md#snappoints) sizes the sheet, and the list scrolls within that height. With `nestedScrollEnabled`, the list scrolls its own content first; once it reaches the top edge, the remaining drag moves the sheet.

```tsx BottomSheetScrollableExample.tsx
import { useState } from 'react';
import { FlatList, Text, useColorScheme } from 'react-native';
import { Host, BottomSheet, Button, RNHostView } from '@expo/ui';

const DATA = Array.from({ length: 50 }, (_, i) => `Item ${i + 1}`);

export default function BottomSheetScrollableExample() {
  const [isPresented, setIsPresented] = useState(false);
  const colorScheme = useColorScheme();
  const ink = { color: colorScheme === 'dark' ? '#FFFFFF' : '#000000' };

  return (
    <>
      <Host matchContents>
        <Button label="Open" onPress={() => setIsPresented(true)} />
      </Host>
      <BottomSheet
        isPresented={isPresented}
        onDismiss={() => setIsPresented(false)}
        snapPoints={['half', 'full']}>
        <RNHostView>
          <FlatList
            nestedScrollEnabled
            style={{ flex: 1 }}
            data={DATA}
            keyExtractor={item => item}
            renderItem={({ item }) => <Text style={[ink, { padding: 16 }]}>{item}</Text>}
          />
        </RNHostView>
      </BottomSheet>
    </>
  );
}
```

## API

```tsx
import { BottomSheet } from '@expo/ui';
```

## Component

### `BottomSheet`

Supported platforms: Android, iOS, Web.

Type: React.[Element](https://www.typescriptlang.org/docs/handbook/jsx.html#function-component)<[BottomSheetProps](/versions/v58.0.0/sdk/ui/universal/bottomsheet.md#bottomsheetprops)\>

A modal sheet that slides up from the bottom of the screen.

Props for the [`BottomSheet`](/versions/v58.0.0/sdk/ui/universal/bottomsheet.md#bottomsheet) component, a modal sheet that slides up from the bottom of the screen.

BottomSheetProps

### `children`

Supported platforms: Android, iOS, Web.

Optional • Type: [ReactNode](https://reactnative.dev/docs/react-node)

Content to render inside the bottom sheet.

### `containerColor`

Supported platforms: Android, iOS 16.4+, Web.

Optional • Type: [ColorValue](https://reactnative.dev/docs/colors)

The sheet's own background color, painting its full chrome (including the drag-indicator zone and, on iOS, the home-indicator safe-area inset). When omitted, each platform keeps its own default.

This only paints the background. `children` are React Native views on every platform, so they don't pick up a contrasting text color automatically -- set one explicitly if you use a dark `containerColor`.

### `contentColor`

Supported platforms: Android.

Optional • Type: [ColorValue](https://reactnative.dev/docs/colors)

The preferred color for native Compose content that doesn't set its own color. Doesn't reach a `BottomSheet`'s (React Native) `children` -- see `containerColor`'s doc.

### `contentPadding`

Supported platforms: Android, iOS, Web.

Optional • Type: [BottomSheetContentPadding](/versions/v58.0.0/sdk/ui/universal/bottomsheet.md#bottomsheetcontentpadding)

Padding between the sheet and [`children`](/versions/v58.0.0/sdk/ui/universal/bottomsheet.md#children), in dp on Android, points on iOS, and CSS pixels on web. Pass `0` for content that reaches the sheet's edges.

When omitted, each platform keeps the inset it applies by default.

Example

\`\`contentPadding={0} `— full-bleed content`

Example

\`\`contentPadding={{ top: 8, bottom: 24 }} `— no horizontal inset`

### `isPresented`

Supported platforms: Android, iOS, Web.

Type: `boolean`

Whether the bottom sheet is currently visible.

### `modifiers`

Supported platforms: Android, iOS, Web.

Optional • Type: `ModifierConfig[]`

Platform-specific modifier escape hatch. Pass an array of modifier configs from `@expo/ui/swift-ui/modifiers` or `@expo/ui/jetpack-compose/modifiers`.

### `onDismiss`

Supported platforms: Android, iOS, Web.

Type: `() => void`

Called when the bottom sheet is dismissed by the user (e.g. swiping down or tapping the overlay).

### `scrimColor`

Supported platforms: Android.

Optional • Type: [ColorValue](https://reactnative.dev/docs/colors)

The color of the scrim overlay rendered behind the bottom sheet. Pass `'transparent'` to make the backdrop invisible while still blocking touches.

### `shouldDismissOnBackPress`

Supported platforms: Android.

Optional • Type: `boolean` • Default: `true`

Whether pressing the Android hardware back button (or back gesture) dismisses the bottom sheet. When `false`, the back press does not dismiss the sheet (note: the press still does not reach React Native's `BackHandler`).

### `shouldDismissOnClickOutside`

Supported platforms: Android.

Optional • Type: `boolean` • Default: `true`

Whether tapping the backdrop (scrim) dismisses the bottom sheet. When `false`, the sheet stays open until the user explicitly closes it (e.g. via a button).

### `showDragIndicator`

Supported platforms: Android, iOS, Web.

Optional • Type: `boolean` • Default: `true`

Whether to show a drag indicator at the top of the sheet.

### `snapPoints`

Supported platforms: Android, iOS, Web.

Optional • Type: [SnapPoint[]](/versions/v58.0.0/sdk/ui/universal/bottomsheet#snappoint)

Heights the sheet can rest at. When omitted, the sheet auto-sizes to its content. See [`SnapPoint`](/versions/v58.0.0/sdk/ui/universal/bottomsheet.md#snappoint) for the supported values.

Example

\`\`['half', 'full'] `— draggable between half and full`

Example

\`\`['full'] `— always full height`

### `testID`

Supported platforms: Android, iOS, Web.

Optional • Type: `string`

Identifier used to locate the component in end-to-end tests.

## Types

### `BottomSheetContentPadding`

Supported platforms: Android, iOS, Web.

Padding between a [`BottomSheet`](/versions/v58.0.0/sdk/ui/universal/bottomsheet.md#bottomsheet) and its content — a single value applied to every edge, or per-edge values where an edge that is left out is `0`.

Type: `number` or `object` shaped as below:

| Property | Type | Description |
| --- | --- | --- |
| bottom(optional) | `number` | - |
| left(optional) | `number` | - |
| right(optional) | `number` | - |
| top(optional) | `number` | - |

### `SnapPoint`

Supported platforms: Android, iOS, Web.

A snap point describing one of the heights a [`BottomSheet`](/versions/v58.0.0/sdk/ui/universal/bottomsheet.md#bottomsheet) can rest at.

-   `'half'` — Approximately half-screen.
-   `'full'` — Fully expanded.
-   `{ fraction }` — A fraction of the screen height (0–1). iOS / web only.
-   `{ height }` — A fixed pixel height. iOS / web only.

On Android, `{ fraction }` and `{ height }` snap to the nearest of `'half'` / `'full'`. See the component docs for platform behavior notes.

Type: `'half'` or `'full'` or `object` shaped as below:

| Property | Type | Description |
| --- | --- | --- |
| fraction | `number` | - |

Or `object` shaped as below:

| Property | Type | Description |
| --- | --- | --- |
| height | `number` | - |
