Docs / Components / 02

NativeKitBottomSheet

Material 3 sheets on Android and UIKit sheets on iOS, with your React Native content rendered inside.

  • 7 variants
  • Action sheet
  • Detents

Usage

A real platform bottom sheet: Material 3 sheets (Jetpack Compose) on Android and UIKit sheets (UISheetPresentationController / UIAlertController) on iOS. Your React Native children are rendered inside the native sheet, so you get native drag physics, detents, scrim, back handling and predictive back, while writing the content in React.

Usage.tsx
import { useState } from 'react';
import { Text } from 'react-native';
import {
  NativeKitBottomSheet,
  NativeKitButton,
} from '@nameisjayant/react-native-nativekit';

const [open, setOpen] = useState(false);

<NativeKitButton title="Show sheet" onPress={() => setOpen(true)} />

<NativeKitBottomSheet
  visible={open}
  variant="halfExpanded"
  onDismiss={() => setOpen(false)}
  style={{ padding: 24, gap: 12 }}
>
  <Text>Any React Native content</Text>
  <NativeKitButton title="Close" onPress={() => setOpen(false)} />
</NativeKitBottomSheet>

The sheet is controlled: show it with visible, and set visible back to false in onDismiss, which fires whenever the user closes the sheet (swipe, scrim tap, back button, or an action-sheet row).

Variants

Variants
<NativeKitBottomSheet variant="modal" />         // sized to its content
<NativeKitBottomSheet variant="halfExpanded" />  // opens at half height, drag up to expand
<NativeKitBottomSheet variant="expanded" />      // opens at full height
<NativeKitBottomSheet variant="fullScreen" />    // covers the whole screen
<NativeKitBottomSheet variant="persistent" />    // non-modal, always on screen
<NativeKitBottomSheet variant="expandable" />    // non-modal, peek / expand / swipe away
<NativeKitBottomSheet variant="actionSheet" actions={[...]} />
VariantAndroid (Compose M3)iOS (UIKit)
modal (default)ModalBottomSheet, wraps contentSheet with a content-fit custom detent (iOS 16+, .large on iOS 15)
halfExpandedModalBottomSheet with skipPartiallyExpanded = false, opens half-expandedDetents [.medium, .large], opens at .medium
expandedModalBottomSheet, full heightDetent [.large]
fullScreenEdge-to-edge ModalBottomSheet, square corners, no drag handle.fullScreen modal presentation
persistentStandard sheet (BottomSheetScaffold), no scrim, can't be hidden by the userUndimmed sheet, detents [peek, content], isModalInPresentation
expandableStandard sheet: collapsed (peek) / expanded / swipe to hideUndimmed sheet, detents [peek, content], swipe to dismiss
actionSheetModalBottomSheet with a Material list of actionsUIAlertController with .actionSheet style (popover on iPad)

persistent and expandable sheets don't block the screen: the app underneath stays scrollable and tappable.

Action sheet

ActionSheet.tsx
<NativeKitBottomSheet
  visible={open}
  variant="actionSheet"
  title="Photo options"
  message="Choose what to do with this photo."
  actions={[
    { title: 'Share', onPress: share },
    { title: 'Archive', disabled: true },
    { title: 'Delete', role: 'destructive', onPress: remove },
    { title: 'Cancel', role: 'cancel' },
  ]}
  onDismiss={() => setOpen(false)}
/>

Choosing an action calls its onPress (and onActionPress(index, action)), then closes the sheet and fires onDismiss. destructive rows are red; the cancel row is separated from the others.

iOS allows a single cancel action; extra ones are shown as regular rows.

Customizing

Customizing
<NativeKitBottomSheet
  visible={open}
  variant="expandable"
  peekHeight={160}            // collapsed height of persistent / expandable sheets
  cornerRadius={28}
  backgroundColor="#FFF8E1"
  showDragHandle={false}
  dismissible={false}         // no swipe / scrim / back dismissal
  onStateChange={(state) => console.log(state)} // 'collapsed' | 'halfExpanded' | 'expanded' | 'hidden'
  onDismiss={() => setOpen(false)}
>
  …
</NativeKitBottomSheet>

Content sizing

The content is laid out at the sheet's width. For halfExpanded, expanded and fullScreen it is stretched to the sheet's height, so flex: 1 children (for example a ScrollView) fill the sheet.

For the other variants the sheet wraps the content, capped at the tallest the sheet can get; give a long ScrollView flexShrink: 1 so it scrolls instead of overflowing:

Scrolling content
<NativeKitBottomSheet visible={open} variant="modal" onDismiss={() => setOpen(false)}>
  <ScrollView style={{ flexShrink: 1 }} contentContainerStyle={{ padding: 24 }}>
    {items.map((item) => (
      <Text key={item.id}>{item.name}</Text>
    ))}
  </ScrollView>
</NativeKitBottomSheet>

Props

PropTypeDefaultDescription
visibleboolean—Whether the sheet is shown. Required.
variant'modal' | 'halfExpanded' | 'expanded' | 'fullScreen' | 'persistent' | 'expandable' | 'actionSheet''modal'Presentation style. See Variants.
dismissiblebooleantrueAllow dismissing by swipe, scrim tap and back button. persistent is never user-dismissible.
showDragHandlebooleantrueDrag handle (Android) / grabber (iOS). Not shown by fullScreen and actionSheet.
cornerRadiusnumberplatformTop corner radius in dp/pt.
backgroundColorColorValueplatformSheet background color.
peekHeightnumber120Collapsed height of persistent / expandable sheets (iOS 16+; .medium on iOS 15).
title / messagestring—actionSheet header.
actionsNativeKitBottomSheetAction[]—actionSheet rows: { title, role?, disabled?, onPress? }.
styleStyleProp<ViewStyle>—Style of the view wrapping children inside the sheet.
onDismiss() => void—The user dismissed the sheet. Set visible to false here.
onStateChange(state) => void—The sheet settled at 'collapsed', 'halfExpanded', 'expanded' or 'hidden'.
onActionPress(index, action) => void—An actionSheet action was chosen.

All types are exported:

TypeScript
import type {
  NativeKitBottomSheetProps,
  NativeKitBottomSheetVariant,
  NativeKitBottomSheetState,
  NativeKitBottomSheetAction,
  NativeKitBottomSheetActionRole,
} from '@nameisjayant/react-native-nativekit';