BottomSheet

A sheet that rises from the bottom edge, built on @gorhom/bottom-sheet.

import { BottomSheet } from "@delacour/native-ui/bottom-sheet";
<BottomSheet>
  <BottomSheet.Trigger asChild>
    <Button>Open</Button>
  </BottomSheet.Trigger>

  <BottomSheet.Portal>
    <BottomSheet.Overlay />
    <BottomSheet.Container>
      <BottomSheet.Content>
        <BottomSheet.Title>Filters</BottomSheet.Title>
        <BottomSheet.Description>Narrow the list down.</BottomSheet.Description>
        <BottomSheet.Close />
      </BottomSheet.Content>
    </BottomSheet.Container>
  </BottomSheet.Portal>
</BottomSheet>

There is no size axis and no colour axis: a sheet has one shape and one surface.

Anatomy

PartWhat it is
BottomSheetState only — the root renders no view at all
BottomSheet.TriggerThe control that opens the sheet. asChild to make a Button the trigger
BottomSheet.PortalEverything drawn above the app. Lifts the overlay out to the container
BottomSheet.OverlayThe scrim. Written beside the container, drawn as its backdrop
BottomSheet.ContainerThe sheet itself. Every gorhom prop passes through it
BottomSheet.ContentThe sheet's body, sized to itself under enableDynamicSizing
BottomSheet.ScrollViewA scrolling body. Needs enableDynamicSizing={false} and snap points
BottomSheet.CloseThe dismiss control, positioned out of the content's flow
BottomSheet.TitleThe sheet's heading — a Text.Header with the close control's clearance
BottomSheet.DescriptionSupporting copy under the title
BottomSheet.FooterControls at the bottom. sticky pins them above the keyboard

Two parts are not drawn where they are written

That is the component. Portal hoists Overlay into gorhom's backdropComponent, and Container hoists a sticky Footer into its footerComponent. Writing them in the tree is what keeps the anatomy readable; hoisting them is what makes gorhom render them correctly.

One callback for every path

onOpenChange fires whenever the sheet opens or closes — a trigger, a swipe down, a press on the backdrop, BottomSheet.Close, or a controlled isOpen.

Deliberately one callback: gorhom's own onClose fires for the gesture alone, which is the shape that makes a caller wire three handlers and still miss one.

Text input inside a sheet

Use useBottomSheetInput() and spread what it returns onto the field. The ref is not optional polish.

import { useBottomSheetInput } from "@delacour/native-ui/bottom-sheet";

const input = useBottomSheetInput();

<Input {...input} value={query} onChangeText={setQuery} />;

The scrim is also rebuilt for this reason: gorhom's full-screen backdrop mounts a Gesture.Tap() that steals a TextInput's tap. BottomSheet.Overlay passes pressBehavior="none" and lays a Pressable only over the band above the sheet, sized from its animated position.

That covers the keyboard half. A drag across a field can still be claimed by the sheet's content pan — pass enableContentPanningGesture={false} on the container when that bites.

Do not put a KeyboardStickyView inside a sheet. It is a third mechanism moving the same view, and it fights the other two every frame.

Accessibility

BottomSheet.Content sets accessible={false} by default, and it is the least obvious line in the component. gorhom marks its content container accessible; on iOS an accessible container collapses its whole subtree into one element, so every field, button and line of copy inside becomes unreachable to VoiceOver.

Gotchas

BottomSheet.Container takes no className

Use backgroundClassName. The container's own style is forwarded to gorhom, which passes [StyleSheet.absoluteFill, backgroundStyle] — dropping it renders the sheet transparent over the app.

Scrolling needs snap points

BottomSheet.ScrollView requires enableDynamicSizing={false} and explicit snapPoints on the container. It also puts its classes on an inner View rather than gorhom's content container: Uniwind compiles contentContainerClassName into an array, gorhom reads paddingBottom off a flattened style, and the last row would hide behind a pinned footer.

Requirements

@gorhom/bottom-sheet is a required peer, and the app must mount DelacourProvider — it supplies the modal provider the sheet presents into.

API

Prop

Type

The root takes no ViewProps — it renders no view. Layout and styling belong on Container, Content and Footer.

BottomSheet.Trigger

Opens the sheet. A Pressable, so it inherits the whole vocabulary — asChild renders into your own element instead of emitting a View.

Prop

Type

BottomSheet.Container

The modal itself. Extends @gorhom/bottom-sheet's BottomSheetModalPropssnapPoints, enableDynamicSizing, index and the rest pass straight through. The component slots (backdropComponent, backgroundComponent, footerComponent, handleComponent) and onDismiss are withheld: the compound parts fill them.

Prop

Type

BottomSheet.Content

The padded region inside the sheet. Extends ViewProps.

Prop

Type

BottomSheet.ScrollView

Scrollable content that hands its gesture to the sheet at the top of the scroll. Extends Gorhom's BottomSheetScrollViewProps; contentContainerStyle and enableFooterMarginAdjustment are withheld — the first has a className equivalent, the second is managed for you.

Prop

Type

BottomSheet.Footer

Pinned below the content.

Prop

Type

BottomSheet.Overlay

The backdrop. Composed in with sensible defaults when the sheet holds none.

Prop

Type

BottomSheet.Title / Description / Close / Handle / Background / Portal

Title and Description are Text presets and take its props. Close is a Pressable that dismisses the sheet, defaulting its accessibilityLabel to "Close" and its feedback to fade. Handle and Background replace the grabber and the surface outright — reach for handleClassName and backgroundClassName on Container first. Portal takes only children.

Prop

Type

On this page