# Bottom sheet

> A sheet that rests at detents, follows the finger 1:1, settles with velocity and hands drags off to its scroll view.

- Type: Component (overlays)
- Access: Free, open source (MIT)
- Page: https://goodui.dev/native/components/bottom-sheet
- Markdown: https://goodui.dev/native/components/bottom-sheet.md
- Registry item: https://goodui.dev/native/r/bottom-sheet.json
- Dependencies: react-native-gesture-handler, react-native-reanimated
- Keywords: react native bottom sheet, expo bottom sheet, draggable sheet, snap points, detents, modal sheet, sheet with scroll view

## When to use

- Secondary tasks on a phone: filters, details, share options, a short form.
- Content that benefits from a peek height before expanding.
- Lists inside a sheet: set scrollable and the sheet and list hand drags off to each other.

## When not to use

- A decision that must interrupt: use an alert dialog.
- A full flow with several steps: push a screen instead.
- A one-line confirmation of a background task: use toast.

## Installation

With the shadcn CLI (needs a components.json in the Expo app), then `npx expo install --fix` so native dependencies match your Expo SDK. The foundation comes along the first time.

```bash
npx shadcn@latest add https://goodui.dev/native/r/bottom-sheet.json
npx expo install --fix
```

The component lands in `components/goodui-native/bottom-sheet/` and imports the foundation from `components/goodui-native/foundation/` with relative paths; it also installs locale.

### Setup

- Wrap the app in GestureHandlerRootView and ThemeProvider.

Built-in strings follow `<GooduiLocaleProvider>` (one [language pack](https://goodui.dev/native/r/locale-tr.json) translates the whole kit); the `labels` prop overrides them for one instance.

## Usage

```tsx
import { useState } from "react";
import { BottomSheet } from "@/components/goodui-native/bottom-sheet/bottom-sheet";
import { Button } from "@/components/goodui-native/button/button";

export function Filters() {
  const [open, setOpen] = useState(false);
  return (
    <>
      <Button onPress={() => setOpen(true)}>Show filters</Button>
      <BottomSheet
        open={open}
        onOpenChange={setOpen}
        title="Filters"
        description="Narrow 128 stays to what fits your trip"
        footer={<Button onPress={() => setOpen(false)}>Show 42 stays</Button>}
      >
        {/* rows */}
      </BottomSheet>
    </>
  );
}
```

## Examples

### A long list that hands off with the sheet

```tsx
<BottomSheet open={open} onOpenChange={setOpen} title="Neighborhoods" detents={[0.45, 0.92]} scrollable>
  {places.map((p) => <Row key={p.id} {...p} />)}
</BottomSheet>
```

### One fixed height

```tsx
<BottomSheet open={open} onOpenChange={setOpen} title="Share trip" detents={[0.32]}>
  ...
</BottomSheet>
```

## API reference

### BottomSheet

`import { BottomSheet } from "@/components/goodui-native/bottom-sheet/bottom-sheet";`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `open` (required) | `boolean` | – | Whether the sheet is shown. Closing animates out before it unmounts. |
| `onOpenChange` (required) | `(open: boolean) => void` | – | Called with false when the person drags it closed, taps the backdrop, presses Escape (web) or back. |
| `children` (required) | `ReactNode` | – | Sheet content. Long content needs `scrollable`. |
| `title` | `string` | – | Heading at the top of the sheet, also used in its accessibility label. |
| `description` | `string` | – | Supporting line under the title. |
| `detents` | `number[]` | – | Resting heights as fractions of the screen height. Default `[0.5, 0.92]`. |
| `initialDetent` | `number` | – | Index into the sorted detents the sheet opens at. |
| `onDetentChange` | `(index: number) => void` | – | Called when the sheet settles on a different detent. |
| `scrollable` | `boolean` | – | Make the body a scroll view that hands off with the sheet: drags move the sheet until it is fully open, then scroll the content; pulling down at the top lowers it again. |
| `footer` | `ReactNode` | – | Content pinned to the bottom of the screen at every detent, such as a primary button. |
| `dismissible` | `boolean` | – | Allow closing by dragging down, tapping the backdrop, Escape on the web or the back button. Default `true`. |
| `labels` | `Partial<BottomSheetLabels>` | – | Overrides for the built-in screen reader strings; a GooduiLocaleProvider pack sets them app-wide. |
| `testID` | `string` | – | Identifier for end-to-end tests. |

## Accessibility

- The grabber is an adjustable control: swipe up or down with a screen reader to change detents; double tap toggles between the smallest and tallest.
- Detent changes are announced. The sheet surface is marked accessibilityViewIsModal.
- The backdrop is a button labelled 'Close sheet' when the sheet is dismissible. Escape on the web and the Android back button close it first.
- On the web the grabber is a slider with the detent as its value (arrow keys resize), and the scroll area is a focusable region named by the title.

## Motion

- One shared value drives the position; drags follow the finger and releases project velocity to pick a detent or close.
- Past the tallest detent the sheet rubber-bands with growing resistance.
- The backdrop opacity is a function of position, so it darkens with the finger.
- Reduced motion: detents snap with a short timing and the sheet fades in and out.

## Haptics

- select when a drag settles on a different detent

## Performance

- A closed sheet mounts nothing: no gestures, listeners or animated styles.
- Gestures and position run on the UI thread; React re-renders only when open changes.

## Notes for AI

- It renders into ThemeProvider's overlay layer, so ThemeProvider must wrap the app. It does not use a native Modal.
- The app root must be wrapped in GestureHandlerRootView.
- Control it with open and onOpenChange. Use footer for a primary action so it stays visible at every detent.
- Set scrollable when content can exceed the sheet; do not nest your own ScrollView inside it.
- Controlled inputs inside the sheet can keep their value in the screen: the overlay layer re-renders in the same commit as its owner, so fast typing never drops characters.

## Related

- [Button](https://goodui.dev/native/components/button.md): A press-responsive button with haptics that runs async actions on its own: it gathers into a spinner, then opens into a success or error state.
- [Toast](https://goodui.dev/native/components/toast.md): Background confirmations that stack at the top, fan out on tap, swipe away up or sideways, and pause their timers while touched.

## Guidance for AI tools

Bottom sheet: A sheet that rests at detents, follows the finger 1:1, settles with velocity and hands drags off to its scroll view. Use only the documented props. Keep accessibility, reduced motion and both color schemes intact when adapting it.

Full library index: https://goodui.dev/native/llms.txt
