---
title: BottomSheet
description: Displays a bottom sheet that slides up from the bottom with animated transitions and swipe-to-dismiss gestures.
links:
  source_native: bottom-sheet/bottom-sheet.tsx
  styles_native: bottom-sheet/bottom-sheet.styles.ts
---

<NativeVideoPlayerView
  target="auto"
  srcLight="https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/native/components/videos/bottom-sheet-docs-light.mp4"
  srcDark="https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/native/components/videos/bottom-sheet-docs-dark.mp4"
/>

## Import

```tsx
import { BottomSheet } from 'heroui-native';
```

## Anatomy

```tsx
<BottomSheet>
  <BottomSheet.Trigger>...</BottomSheet.Trigger>
  <BottomSheet.Portal>
    <BottomSheet.Overlay>...</BottomSheet.Overlay>
    <BottomSheet.Content>
      <BottomSheet.Close />
      <BottomSheet.Title>...</BottomSheet.Title>
      <BottomSheet.Description>...</BottomSheet.Description>
    </BottomSheet.Content>
  </BottomSheet.Portal>
</BottomSheet>
```

- **BottomSheet**: Root component that manages open state and provides context to child components.
- **BottomSheet.Trigger**: Pressable element that opens the bottom sheet when pressed.
- **BottomSheet.Portal**: Renders bottom sheet content in a portal with full window overlay.
- **BottomSheet.Overlay**: Background overlay that covers the screen, typically closes bottom sheet when pressed.
- **BottomSheet.Content**: Main bottom sheet container using @gorhom/bottom-sheet for rendering with gesture support.
- **BottomSheet.Close**: Close button for the bottom sheet. Can accept custom children or uses default close icon.
- **BottomSheet.Title**: Bottom sheet title text with semantic heading role and accessibility linking.
- **BottomSheet.Description**: Bottom sheet description text that provides additional context with accessibility linking.

## Usage

### Basic Bottom Sheet

Simple bottom sheet with title, description, and close button.

```tsx
<BottomSheet>
  <BottomSheet.Trigger asChild>
    <Button>Open Bottom Sheet</Button>
  </BottomSheet.Trigger>
  <BottomSheet.Portal>
    <BottomSheet.Overlay />
    <BottomSheet.Content>
      <BottomSheet.Close />
      <BottomSheet.Title>...</BottomSheet.Title>
      <BottomSheet.Description>...</BottomSheet.Description>
    </BottomSheet.Content>
  </BottomSheet.Portal>
</BottomSheet>
```

### Detached Bottom Sheet

Bottom sheet that appears detached from the bottom edge with custom spacing.

```tsx
<BottomSheet>
  <BottomSheet.Trigger>...</BottomSheet.Trigger>
  <BottomSheet.Portal>
    <BottomSheet.Overlay />
    <BottomSheet.Content
      detached={true}
      bottomInset={insets.bottom + 12}
      className="mx-4"
      backgroundClassName="rounded-[32px]"
    >
      ...
    </BottomSheet.Content>
  </BottomSheet.Portal>
</BottomSheet>
```

### Scrollable with Snap Points

Bottom sheet with multiple snap points and scrollable content.

To make scrollable content work correctly inside `BottomSheet.Content`, follow these base principles:

- Use a scrollable from [`@gorhom/bottom-sheet`](https://gorhom.dev/react-native-bottom-sheet/components/bottomsheetscrollview) (e.g. `BottomSheetScrollView`, `BottomSheetFlatList`, `BottomSheetSectionList`, `BottomSheetVirtualizedList`). A plain `ScrollView`/`FlatList` from `react-native` will let the sheet intercept the scroll gesture.
- On `BottomSheet.Content`, disable over-drag and dynamic sizing so the sheet does not grow with its content or absorb the scroll: `enableOverDrag={false}` and `enableDynamicSizing={false}`.
- Give `BottomSheet.Content` a fixed height via `contentContainerClassName="h-full"` (or any other fixed height). The constraint must be on `BottomSheet.Content`, not on the scrollable child — the scrollable needs a bounded parent to scroll inside.

```tsx
import { BottomSheetScrollView } from '@gorhom/bottom-sheet';

<BottomSheet>
  <BottomSheet.Trigger>...</BottomSheet.Trigger>
  <BottomSheet.Portal>
    <BottomSheet.Overlay />
    <BottomSheet.Content
      snapPoints={['40%', '80%']}
      enableOverDrag={false}
      enableDynamicSizing={false}
      contentContainerClassName="h-full"
    >
      <BottomSheetScrollView>...</BottomSheetScrollView>
    </BottomSheet.Content>
  </BottomSheet.Portal>
</BottomSheet>;
```

See the full example with a sticky footer (`BottomSheetFooter`) in the [GitHub repository](<https://github.com/heroui-inc/heroui-native/blob/main/example/src/components/bottom-sheet/scrollable-with-snap-points.tsx>).

### Blur Overlay

Use the built-in `blur` variant for an animated blur backdrop. iOS only, requires the optional `expo-blur` package; other platforms (or a missing package) fall back to the default solid backdrop. When the library theme is `glass`, the blur variant is used by default.

```tsx
<BottomSheet>
  <BottomSheet.Trigger>...</BottomSheet.Trigger>
  <BottomSheet.Portal>
    <BottomSheet.Overlay variant="blur" blurViewProps={{ intensity: 40 }} />
    <BottomSheet.Content>...</BottomSheet.Content>
  </BottomSheet.Portal>
</BottomSheet>
```

### Custom Overlay

Replace the default overlay with custom content like blur effects.

```tsx
import { useBottomSheet, useBottomSheetAnimation } from 'heroui-native';
import { StyleSheet, Pressable } from 'react-native';
import { interpolate, useDerivedValue } from 'react-native-reanimated';
import { AnimatedBlurView } from './animated-blur-view';
import { useUniwind } from 'uniwind';

export const BottomSheetBlurOverlay = () => {
  const { theme } = useUniwind();
  const { onOpenChange } = useBottomSheet();
  const { progress } = useBottomSheetAnimation();

  const blurIntensity = useDerivedValue(() => {
    return interpolate(progress.get(), [0, 1, 2], [0, 40, 0]);
  });

  return (
    <Pressable
      style={StyleSheet.absoluteFill}
      onPress={() => onOpenChange(false)}
    >
      <AnimatedBlurView
        blurIntensity={blurIntensity}
        tint={theme === 'dark' ? 'dark' : 'systemUltraThinMaterialDark'}
        style={StyleSheet.absoluteFill}
      />
    </Pressable>
  );
};
```

```tsx
<BottomSheet>
  <BottomSheet.Trigger>...</BottomSheet.Trigger>
  <BottomSheet.Portal>
    <BottomSheetBlurOverlay />
    <BottomSheet.Content>...</BottomSheet.Content>
  </BottomSheet.Portal>
</BottomSheet>
```

### With Keyboard-Aware Input

When rendering an `Input` or `InputOTP` inside `BottomSheet.Content`, use the `useBottomSheetAwareHandlers` hook to wire keyboard avoidance handlers. Pass the returned `onFocus` / `onBlur` to your input.

> **Note**: `useBottomSheetAwareHandlers` must be used inside a `BottomSheet`. Call it from a child component rendered inside `BottomSheet.Content` — outside of a `BottomSheet` context the returned handlers are no-ops.

For scrollable content, also configure `BottomSheet.Content` with `keyboardBehavior="extend"` (or `"interactive"`) and `keyboardShouldPersistTaps="handled"` on the scrollable so taps don't dismiss the keyboard before reaching their target.

```tsx
import { BottomSheet, Input, useBottomSheetAwareHandlers } from 'heroui-native';

const BottomSheetTextInput = () => {
  const { onFocus, onBlur } = useBottomSheetAwareHandlers();

  return <Input placeholder="Type here..." onFocus={onFocus} onBlur={onBlur} />;
};

<BottomSheet>
  <BottomSheet.Trigger>...</BottomSheet.Trigger>
  <BottomSheet.Portal>
    <BottomSheet.Overlay />
    <BottomSheet.Content keyboardBehavior="extend">
      <BottomSheetTextInput />
    </BottomSheet.Content>
  </BottomSheet.Portal>
</BottomSheet>;
```

See full examples for [`Input`](<https://github.com/heroui-inc/heroui-native/blob/main/example/src/components/bottom-sheet/with-text-input.tsx>) and [`InputOTP`](<https://github.com/heroui-inc/heroui-native/blob/main/example/src/components/bottom-sheet/with-otp-input.tsx>) inside a bottom sheet.

## Example

```tsx
import { BottomSheet, Button } from 'heroui-native';
import { useState } from 'react';
import { View } from 'react-native';
import { withUniwind } from 'uniwind';
import Ionicons from '@expo/vector-icons/Ionicons';

const StyledIonicons = withUniwind(Ionicons);

export default function BottomSheetExample() {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <BottomSheet isOpen={isOpen} onOpenChange={setIsOpen}>
      <BottomSheet.Trigger asChild>
        <Button variant="secondary">Open Bottom Sheet</Button>
      </BottomSheet.Trigger>
      <BottomSheet.Portal>
        <BottomSheet.Overlay />
        <BottomSheet.Content>
          <View className="items-center mb-5">
            <View className="size-20 items-center justify-center rounded-full bg-green-500/10">
              <StyledIonicons
                name="shield-checkmark"
                size={40}
                className="text-green-500"
              />
            </View>
          </View>
          <View className="mb-8 gap-2 items-center">
            <BottomSheet.Title className="text-center">
              Keep yourself safe
            </BottomSheet.Title>
            <BottomSheet.Description className="text-center">
              Update your software to the latest version for better security and
              performance.
            </BottomSheet.Description>
          </View>
          <View className="gap-3">
            <Button onPress={() => setIsOpen(false)}>Update Now</Button>
            <Button variant="tertiary" onPress={() => setIsOpen(false)}>
              Later
            </Button>
          </View>
        </BottomSheet.Content>
      </BottomSheet.Portal>
    </BottomSheet>
  );
}
```

You can find more examples in the [GitHub repository](https://github.com/heroui-inc/heroui-native/blob/main/example/src/app/(home)/components/bottom-sheet.tsx).

## API Reference

### BottomSheet

| prop            | type                       | default | description                                        |
| --------------- | -------------------------- | ------- | -------------------------------------------------- |
| `children`      | `React.ReactNode`          | -       | Bottom sheet content and trigger elements          |
| `isOpen`        | `boolean`                  | -       | Controlled open state of the bottom sheet          |
| `isDefaultOpen` | `boolean`                  | `false` | Initial open state when uncontrolled               |
| `animation`     | `AnimationRootDisableAll`  | -       | Animation configuration                            |
| `onOpenChange`  | `(value: boolean) => void` | -       | Callback when open state changes                   |
| `...ViewProps`  | `ViewProps`                | -       | All standard React Native View props are supported |

#### Animation Configuration

Animation configuration for bottom sheet root component. Can be:

- `"disable-all"`: Disable all animations including children
- `undefined`: Use default animations

### BottomSheet.Trigger

| prop                       | type                    | default | description                                                    |
| -------------------------- | ----------------------- | ------- | -------------------------------------------------------------- |
| `children`                 | `React.ReactNode`       | -       | Trigger element content                                        |
| `asChild`                  | `boolean`               | -       | Render as child element without wrapper                        |
| `...TouchableOpacityProps` | `TouchableOpacityProps` | -       | All standard React Native TouchableOpacity props are supported |

### BottomSheet.Portal

| prop                       | type                   | default | description                                                                                                                   |
| -------------------------- | ---------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `children`                 | `React.ReactNode`      | -       | Portal content (overlay and bottom sheet)                                                                                     |
| `disableFullWindowOverlay` | `boolean`              | `false` | When true on iOS, uses View instead of FullWindowOverlay. Enables element inspector; overlay won't appear above native modals |
| `unstable_accessibilityContainerViewIsModal` | `boolean` | `false` | Controls whether VoiceOver treats the overlay window as a modal container. When `true`, VoiceOver is restricted to elements inside the overlay. iOS only. Unstable: may change with react-native-screens updates |
| `className`                | `string`               | -       | Additional CSS classes for portal container                                                                                   |
| `style`                    | `StyleProp<ViewStyle>` | -       | Additional styles for portal container                                                                                        |
| `hostName`                 | `string`               | -       | Optional portal host name for specific container                                                                              |
| `forceMount`               | `boolean`              | -       | Force mount when closed for animation purposes                                                                                |

### BottomSheet.Overlay

| prop                    | type                                                   | default | description                                                  |
| ----------------------- | ------------------------------------------------------ | ------- | ------------------------------------------------------------ |
| `children`              | `React.ReactNode`                                      | -       | Custom overlay content                                       |
| `className`             | `string`                                               | -       | Additional CSS classes for overlay                           |
| `style`                 | `ViewStyle`                                            | -       | Additional styles for overlay container                      |
| `animation`             | `Omit<PopupOverlayAnimation, 'entering' \| 'exiting'>` | -       | Animation configuration                                      |
| `variant`               | `'default' \| 'blur'`                                  | `'default'` (`'blur'` when the library theme is `glass`) | Overlay variant. `'blur'` renders an animated blur backdrop (iOS only, requires `expo-blur`; falls back to `'default'` otherwise) |
| `blurViewProps`         | `BlurViewProps`                                        | -       | Props forwarded to the BlurView rendered by the `'blur'` variant. `intensity` sets the maximum animated blur intensity (default: 75 dark / 50 light) |
| `isAnimatedStyleActive` | `boolean`                                              | `true` (`false` for the `'blur'` variant) | Whether animated styles (react-native-reanimated) are active |
| `isCloseOnPress`        | `boolean`                                              | `true`  | Whether pressing overlay closes bottom sheet                 |
| `...PressableProps`     | `PressableProps`                                       | -       | All standard React Native Pressable props are supported      |

#### Animation Configuration

Animation configuration for bottom sheet overlay component. Can be:

- `false` or `"disabled"`: Disable all animations
- `true` or `undefined`: Use default animations
- `object`: Custom animation configuration (excluding `entering` and `exiting` properties)

| prop            | type                       | default     | description                                     |
| --------------- | -------------------------- | ----------- | ----------------------------------------------- |
| `state`         | `'disabled' \| boolean`    | -           | Disable animations while customizing properties |
| `opacity.value` | `[number, number, number]` | `[0, 1, 0]` | Opacity values [idle, open, close]              |

### BottomSheet.Content

| prop                        | type                                     | default | description                                                                                        |
| --------------------------- | ---------------------------------------- | ------- | -------------------------------------------------------------------------------------------------- |
| `children`                  | `React.ReactNode`                        | -       | Bottom sheet content                                                                               |
| `className`                 | `string`                                 | -       | Additional CSS classes for bottom sheet container                                                  |
| `containerClassName`        | `string`                                 | -       | Additional CSS classes for container                                                               |
| `contentContainerClassName` | `string`                                 | -       | Additional CSS classes for content container                                                       |
| `backgroundClassName`       | `string`                                 | -       | Additional CSS classes for background                                                              |
| `handleClassName`           | `string`                                 | -       | Additional CSS classes for handle                                                                  |
| `handleIndicatorClassName`  | `string`                                 | -       | Additional CSS classes for handle indicator                                                        |
| `contentContainerProps`     | `Omit<BottomSheetViewProps, 'children'>` | -       | Props for the content container                                                                    |
| `animation`                 | `AnimationDisabled`                      | -       | Animation configuration                                                                            |
| `...GorhomBottomSheetProps` | `Partial<GorhomBottomSheetProps>`        | -       | All [@gorhom/bottom-sheet props](https://gorhom.dev/react-native-bottom-sheet/props) are supported |

**Note**: You can use all components from [@gorhom/bottom-sheet](https://gorhom.dev/react-native-bottom-sheet/components/bottomsheetview) inside the content, such as `BottomSheetView`, `BottomSheetScrollView`, `BottomSheetFlatList`, etc.

### BottomSheet.Close

BottomSheet.Close extends [CloseButton](./close-button) and automatically handles bottom sheet dismissal when pressed.

### BottomSheet.Title

| prop           | type              | default | description                                        |
| -------------- | ----------------- | ------- | -------------------------------------------------- |
| `children`     | `React.ReactNode` | -       | Title content                                      |
| `className`    | `string`          | -       | Additional CSS classes for title                   |
| `...TextProps` | `TextProps`       | -       | All standard React Native Text props are supported |

### BottomSheet.Description

| prop           | type              | default | description                                        |
| -------------- | ----------------- | ------- | -------------------------------------------------- |
| `children`     | `React.ReactNode` | -       | Description content                                |
| `className`    | `string`          | -       | Additional CSS classes for description             |
| `...TextProps` | `TextProps`       | -       | All standard React Native Text props are supported |

## Hooks

### useBottomSheet

Hook to access bottom sheet primitive context.

```tsx
const { isOpen, onOpenChange } = useBottomSheet();
```

| property       | type                       | description                   |
| -------------- | -------------------------- | ----------------------------- |
| `isOpen`       | `boolean`                  | Current open state            |
| `onOpenChange` | `(value: boolean) => void` | Function to change open state |

### useBottomSheetAnimation

Hook to access bottom sheet animation context for advanced customization.

```tsx
const { progress } = useBottomSheetAnimation();
```

| property   | type                  | description                                  |
| ---------- | --------------------- | -------------------------------------------- |
| `progress` | `SharedValue<number>` | Animation progress (0=idle, 1=open, 2=close) |

### useBottomSheetAwareHandlers

Hook that returns `onFocus` and `onBlur` handlers for keyboard avoidance when an `Input` or `InputOTP` is rendered inside `BottomSheet.Content`. Must be used inside a `BottomSheet` — outside of a `BottomSheet` context, the returned handlers are no-ops.

```tsx
const { onFocus, onBlur } = useBottomSheetAwareHandlers();
```

| property  | type                      | description                                                                          |
| --------- | ------------------------- | ------------------------------------------------------------------------------------ |
| `onFocus` | `(e: FocusEvent) => void` | Focus handler that notifies the bottom sheet about the keyboard target               |
| `onBlur`  | `(e: BlurEvent) => void`  | Blur handler that conditionally clears the keyboard target in the bottom sheet state |

## Special Notes

### Element Inspector (iOS)

BottomSheet uses FullWindowOverlay on iOS, which renders in a separate native window. This breaks the React Native element inspector. To enable the inspector during development, set `disableFullWindowOverlay={true}` on `BottomSheet.Portal`. Tradeoff: the bottom sheet will not appear above native modals when disabled.

### Handling Close Callbacks

It's recommended to use `BottomSheet`'s `onOpenChange` prop for handling close callbacks, as it reliably fires for all close scenarios (swiping down, pressing overlay, pressing close button, programmatic close, etc.).

```tsx
<BottomSheet
  isOpen={isOpen}
  onOpenChange={(value) => {
    setIsOpen(value);
    if (!value) {
      // This callback runs whenever the bottom sheet closes
      // regardless of how it was closed
      yourCallbackOnClose();
    }
  }}
>
  ...
</BottomSheet>
```

**Note**: `BottomSheet.Content`'s `onClose` prop (from @gorhom/bottom-sheet) has limitations and will only fire when the bottom sheet is closed by swiping down. It won't fire when closing via overlay press, close button, or programmatic close. For reliable close callbacks, always use `BottomSheet`'s `onOpenChange` prop instead.
