Skip to main content
Version: v0.4.x

BottomSheet

BottomSheet is the React Native bottom sheet every picker sheet is built on, exported so your own content can use it: filters, menus, confirmations, forms. It slides up over a dimmed backdrop, can be dragged down to dismiss, and is built on React Native's Modal, Animated and PanResponder only.

import { BottomSheet } from '@elik56a/react-native-pickers';
Result
Loading...
Live Editor
import { useState } from 'react';
import { Pressable, Text, View } from 'react-native';
import { BottomSheet } from '@elik56a/react-native-pickers';

export default function Example() {
  const [open, setOpen] = useState(false);
  return (
    <>
      <Pressable onPress={() => setOpen(true)} style={{ backgroundColor: '#C8460D', padding: 14, borderRadius: 12 }}>
        <Text style={{ color: 'white', fontWeight: '600', textAlign: 'center' }}>Open sheet</Text>
      </Pressable>
      <BottomSheet
        visible={open}
        onClose={() => setOpen(false)}
        title="Share"
        footer={
          <Pressable onPress={() => setOpen(false)} style={{ backgroundColor: '#F4F2EE', padding: 14, borderRadius: 12 }}>
            <Text style={{ textAlign: 'center', fontWeight: '600' }}>Close</Text>
          </Pressable>
        }
      >
        <View style={{ padding: 16, gap: 12 }}>
          {['Copy link', 'Send via message', 'Share to…'].map((a) => (
            <Text key={a} style={{ fontSize: 16 }}>{a}</Text>
          ))}
        </View>
      </BottomSheet>
    </>
  );
}

Try the props​

presentation
<BottomSheet
visible={open}
onClose={() => setOpen(false)}
title="Share"
draggable
>
…
</BottomSheet>

Bottom sheet or modal​

presentation switches between a bottom sheet and a centered modal:

  • 'auto' (default): a modal on web windows wider than the sheet (688 px), a bottom sheet everywhere else.
  • 'bottomSheet': always slides up from the bottom, with a drag handle.
  • 'modal': always a centered dialog that fades in, with no drag handle or swipe-to-close.
<BottomSheet presentation="modal" visible={open} onClose={close} title="Confirm">
…
</BottomSheet>

Opening and closing​

BottomSheet is fully controlled: render it with visible and change visible in onClose. onClose is called when the user:

  • taps the backdrop (closeOnBackdropPress, default true)
  • drags the sheet down past about 30% of its height (max 140 px), or flicks it down; on the web, scrolls the wheel down over it (enableSwipeToClose, default true)
  • presses Android's back button

When visible turns false, the sheet animates out and then unmounts its children. onOpened and onClosed fire when the animations finish, which is handy for focusing inputs or running work after the sheet is gone.

The header row shows when you pass title, headerLeft or headerRight. A string title is styled for you (use the title slot); a node is rendered as-is. The drag handle and header are both drag areas.

<BottomSheet
visible={open}
onClose={close}
title="Filters"
headerLeft={<Button title="Reset" onPress={reset} />}
headerRight={<Button title="Apply" onPress={apply} />}
>
…
</BottomSheet>

Height​

  • Default: the sheet sizes to its content, up to maxHeightRatio (default 0.9) of the window.
  • height ≀ 1: a fraction of the window height (height={0.6} β†’ 60%).
  • height > 1: pixels.

With a fixed height, the content area becomes flex: 1, so a FlatList or ScrollView inside fills it and scrolls.

bottomInset adds bottom padding for the home indicator (default 24 on iOS, 12 elsewhere). Pass your safe-area inset for pixel-perfect results:

const insets = useSafeAreaInsets();
<BottomSheet bottomInset={insets.bottom + 8} … />

On tablets and the web the sheet is centred with a maximum width of 640.

Expanding to full screen​

The sheet can grow past its content height to expandedHeight (default: the whole window below the status bar), and collapse back:

ExpandCollapse, then close
iOS / AndroidDrag the handle or header upDrag it down
WebWheel up over the sheet, or drag the handle upWheel down, or drag the handle down

Released past halfway, or with a flick, the sheet snaps to the expanded height; otherwise it settles back. A list or scroll view inside keeps the wheel until it reaches its top or bottom, then the sheet takes over.

The handle always drags. On the web the header row doesn't by default (draggable={false}), so mouse drags there still select text; pass draggable to make the whole header a drag area. expandable={false} keeps the sheet at its content height on every platform.

Try it: open the sheet, then scroll the wheel up over it (or drag the header, since this example turns draggable on). Wide web windows show a centered modal by default, so the examples here force presentation="bottomSheet":

Result
Loading...
Live Editor
import { useState } from 'react';
import { Pressable, ScrollView, Text, View } from 'react-native';
import { BottomSheet } from '@elik56a/react-native-pickers';

const comments = Array.from({ length: 30 }, (_, i) => `Comment #${i + 1}: looks great!`);

export default function Example() {
  const [open, setOpen] = useState(false);
  return (
    <>
      <Pressable onPress={() => setOpen(true)} style={{ backgroundColor: '#C8460D', padding: 14, borderRadius: 12 }}>
        <Text style={{ color: 'white', fontWeight: '600', textAlign: 'center' }}>Open comments</Text>
      </Pressable>
      <BottomSheet
        visible={open}
        onClose={() => setOpen(false)}
        title="Comments"
        presentation="bottomSheet"
        draggable
        height={0.45}
        expandedHeight={0.9}
      >
        <ScrollView contentContainerStyle={{ padding: 16, gap: 14 }}>
          {comments.map((c) => (
            <Text key={c} style={{ fontSize: 15 }}>{c}</Text>
          ))}
        </ScrollView>
      </BottomSheet>
    </>
  );
}

The list scrolls first; once it's back at the top, wheeling up expands the sheet, and wheeling down at the bottom collapses it, then closes it.

Configuring it​

// Expand to 85% of the window instead of full screen
<BottomSheet expandedHeight={0.85} … />

// Expand to a fixed height in px
<BottomSheet expandedHeight={600} … />

// Mouse dragging on the web too
<BottomSheet draggable … />

// No expanding: drag (or wheel) down still closes it
<BottomSheet expandable={false} … />

// Fixed at its height: no expanding, no swipe or wheel to close
<BottomSheet expandable={false} enableSwipeToClose={false} … />

Every picker sheet takes the same props through sheetProps:

<DatePickerSheet visible={open} onClose={close} sheetProps={{ expandedHeight: 0.8 }} … />

Keyboard​

On iOS the sheet is wrapped in a KeyboardAvoidingView, so text inputs inside stay visible above the keyboard.

Props​

PropTypeDefaultDescription
visiblebooleanrequiredShow or hide (animated).
onClose() => voidrequiredRequested close: backdrop, swipe, Android back.
childrenReactNoderequiredSheet content.
titleReactNodeβ€”Header title; strings get the title style.
headerLeftReactNodeβ€”Left side of the header.
headerRightReactNodeβ€”Right side of the header.
footerReactNodeβ€”Content pinned below the children.
presentation'auto' | 'bottomSheet' | 'modal''auto'Bottom sheet or centered modal; auto is a modal on wide web windows.
enableSwipeToClosebooleantrueDrag down (wheel down on the web) to dismiss (bottom sheet only).
draggablebooleantrue native / false webThe header row drags too, not just the handle.
expandablebooleantrueGrow to expandedHeight: drag up on native, wheel up on the web.
expandedHeightnumberwindow βˆ’ status barExpanded height: fraction (≀ 1) or px.
closeOnBackdropPressbooleantrueTap outside to dismiss.
maxHeightRationumber0.9Max height as a fraction of the window.
heightnumbercontentFixed height: fraction (≀ 1) or px.
bottomInsetnumber24 iOS / 12Bottom padding.
onOpened() => voidβ€”Open animation finished.
onClosed() => voidβ€”Close animation finished (content unmounted).
stylesSlotStyles<BottomSheetSlot>β€”Slots below.
themeDeepPartial<PickerTheme>β€”Theme override (sheet, backdrop, handle, radius.sheet…).
testIDstringβ€”Test ID on the modal.

Slots​

SlotElement
backdropDimmed overlay
sheetThe sheet surface
handleGrab handle
headerHeader row
titleString title text
contentChildren wrapper
footerFooter wrapper

Theme tokens used​

colors.sheet (background), colors.backdrop, colors.handle, radius.sheet (top corners), animation.duration.