tetra-ui Logotetra ui

Bottom Sheet

An interactive bottom sheet built on Expo UI with native snap points, gestures, and keyboard support.

Bottom Sheet is built on @expo/ui native bottom sheets — SwiftUI on iOS and Jetpack Compose on Android. Use it for draggable snap points, scrollable content, keyboard-aware forms, or simple modal sheets.

bottom-sheet

Installation

npx shadcn@latest add @tetra-ui/bottom-sheet

Usage

import { Button, ButtonText } from "@/components/ui/button";
import {
  BottomSheet,
  BottomSheetBody,
  BottomSheetClose,
  BottomSheetContent,
  BottomSheetFooter,
  BottomSheetHeader,
  BottomSheetTitle,
  BottomSheetTrigger,
} from "@/components/ui/bottom-sheet";
<BottomSheet>
  <BottomSheetTrigger asChild>
    <Button>
      <ButtonText>Open</ButtonText>
    </Button>
  </BottomSheetTrigger>
  <BottomSheetContent>
    <BottomSheetHeader>
      <BottomSheetTitle>Bottom Sheet Title</BottomSheetTitle>
    </BottomSheetHeader>
    <BottomSheetBody>
      <Text className="text-foreground">Bottom Sheet Body</Text>
    </BottomSheetBody>
    <BottomSheetFooter>
      <BottomSheetClose asChild>
        <Button>
          <ButtonText>Close</ButtonText>
        </Button>
      </BottomSheetClose>
    </BottomSheetFooter>
  </BottomSheetContent>
</BottomSheet>

Snap points

Pass Expo UI SnapPoint values to BottomSheetContent:

  • "half" — medium detent (iOS) / partial expansion (Android)
  • "full" — large detent (iOS) / full expansion (Android)
  • { fraction: 0.4 } — fractional height
  • { height: 320 } — fixed height in points
<BottomSheetContent snapPoints={[{ fraction: 1 / 3 }, "half", "full"]}>
  {/* ... */}
</BottomSheetContent>

Omit snapPoints to auto-size the sheet to its content.

On iOS, multiple detents are supported natively. On Android, fractional and height snap points map to the nearest partial or expanded state.

Scrollable content

Use BottomSheetScrollView for long lists. Place BottomSheetHeader as a sibling above the scroll view:

<BottomSheetContent snapPoints={["half", "full"]}>
  <BottomSheetHeader>
    <BottomSheetTitle>List</BottomSheetTitle>
  </BottomSheetHeader>
  <BottomSheetScrollView>
    {/* scrollable items */}
  </BottomSheetScrollView>
</BottomSheetContent>

Keyboard

The native sheet handles keyboard avoidance automatically. BottomSheetFooter stays above the keyboard without manual input hooks.

BottomSheetHeader, BottomSheetScrollView, and BottomSheetFooter must be direct children of BottomSheetContent (fragments are fine). On Android, children are split by component type so the footer can sit outside the scroll area in the Compose layout.

<BottomSheetContent snapPoints={["half", "full"]}>
  <BottomSheetHeader>
    <BottomSheetTitle>Send a message</BottomSheetTitle>
  </BottomSheetHeader>
  <BottomSheetScrollView>
    <TextInput placeholder="Message" />
  </BottomSheetScrollView>
  <BottomSheetFooter>
    <Button>Send</Button>
  </BottomSheetFooter>
</BottomSheetContent>

Changelog

2026-05-25 - Initial release

  • Compound API: BottomSheet, BottomSheetContent, and layout primitives
  • Snap points, pan-down-to-close, dynamic sizing
  • BottomSheetScrollView, sticky BottomSheetFooter

2026-06-22 - Keyboard Controller integration

  • Added react-native-keyboard-controller as a dependency for footer keyboard avoidance on iOS.

2026-07-08 - Expo UI rewrite

  • Rebuilt on @expo/ui native bottom sheets (SwiftUI on iOS, Jetpack Compose on Android)
  • Removed BottomSheetPortal, BottomSheetOverlay, useBottomSheet, useBottomSheetAnimation, and useBottomSheetInputHandlers
  • Snap points now use Expo UI SnapPoint format ("half", "full", { fraction }, { height })
  • No portal or overlay required — the native sheet is the modal

On this page