React Native TrueSheet
repository·main·Indexed 24 days ago
https://github.com/lodev09/react-native-true-sheetA high-performance native bottom sheet experience for React Native applications built on the Fabric architecture. It supports imperative control via present/dismiss methods, a dedicated Sheet Navigator for React Navigation and Expo Router, and advanced features like Android sheet stacking, smooth dimming, and Reanimated integration. Version 3.0+ requires React Native 0.81+ and the New Architecture (Fabric).
What's inside @lodev09/react-native-true-sheet
- React Native TrueSheet provides a truly native bottom sheet experience for React Native applications. Unlike many libraries that use JavaScript-based animations, TrueSheet is implemented in the native realm (iOS and Android) with support for React Native's Fabric architecture. This ensures maximum performance, native accessibility, and native screen reader support.
Understand the example project structure
mainThe
example/directory is organized into three main parts:bare/: A Bare React Native application using thereact-native-community/cli.expo/: An Expo Router application.shared/: Contains shared components and utilities used across the different example apps.
example/ ├── bare/ # Bare React Native app (react-native-community/cli) ├── expo/ # Expo Router app └── shared/ # Shared components and utilitiesNatural Sheet Stacking on Android
mainIn version 3.4 and later, presenting a sheet on top of another sheet on Android triggers a stacking effect where the parent sheet slides down instead of disappearing. This stacking effect is reactive: if the child sheet is resized, the parent sheet follows in real-time to maintain the visual hierarchy.Configure Detents
mainDetents define the snap points (heights) for the sheet. You can provide up to 3 detents, which must be sorted from smallest to largest.
Supported values:
'auto': Fits the content size (iOS 16+, Android, Web).0–1: A fraction of the screen height.
Critical Rule: Never combine
'auto'with thescrollableprop. Auto-sizing requires measuring full content, which is incompatible with the clipping behavior of a scrollable sheet. Use fractional detents for scrollable sheets instead.// Content-sized sheet <TrueSheet detents={['auto']} /> // Half and full screen <TrueSheet detents={[0.5, 1]} /> // Three stops: peek, half, full <TrueSheet detents={['0.25', '0.5', '1']} />How TrueSheet handles keyboard visibility
mainTrueSheet provides native keyboard avoidance on both iOS and Android without additional configuration. When a
TextInputinside the sheet is focused, the sheet automatically adjusts to keep the input visible above the keyboard.- iOS: Leverages
UISheetPresentationController's built-in keyboard avoidance. - Android: Uses
WindowInsetsAnimationCompatto track keyboard height and reconfigure sheet detents in real-time.
const App = () => { return ( <TrueSheet ref={sheet} detents={['auto']}> <View style={{ padding: 16 }}> <TextInput placeholder="Type something..." style={{ borderWidth: 1, borderColor: '#ccc', padding: 12, borderRadius: 8 }} /> </View> </TrueSheet> ) }- iOS: Leverages
Handle Position events and realtime updates
mainThe
onPositionChangeevent provides continuous updates during drags or animations. It uses thePositionChangeEventPayload.Key Concept:
realtimefield Therealtimeboolean in the payload is critical for performance:- Use
realtime: true(native animation frames) when driving Reanimated worklets. - Use
realtime: false(JS-driven updates) when you need to drive JS-based animations.
// PositionChangeEventPayload structure { index: number // Continuous float — 0.5 means halfway between detent 0 and 1 position: number detent: number realtime: boolean // true = native animation frame, false = JS-driven }- Use
Choose the Right TrueSheet Control Pattern
mainSelect an integration pattern based on your platform requirements and where the sheet trigger is located in your component tree:
- Ref: Use when the trigger and sheet are in the same component. Works on all platforms.
- Named + global methods: Use when the trigger is far from the sheet (e.g., different screen). Native only.
TrueSheetProvider+useTrueSheet(): Required for Web support or if you prefer hook-based control. Works on all platforms.createTrueSheetNavigator(): Use when sheets are part of a navigation flow. Works on all platforms.ReanimatedTrueSheet: Use when you need animated values synced to the sheet position. Works on all platforms.
Stacking multiple sheets
mainTrueSheet automatically handles sheet stacking. When you present a new sheet while another is already visible, the currently visible sheet is hidden (unless it is fully expanded, in which case the new sheet simply presents on top). When the top sheet is dismissed, the previous sheet is automatically shown again. Sheets do not need to be in a parent-child relationship in the component tree; they can be defined anywhere.
const presentSheet2 = async () => { await sheet2.current?.present() // Sheet 2 will present, Sheet 1 will be hidden } return ( <> <TrueSheet ref={sheet1}> <Button onPress={presentSheet2} title="Present Sheet 2" /> <View /> </TrueSheet> <TrueSheet ref={sheet2}> <View /> </TrueSheet> </> )Automatic Edge-to-Edge Detection (Android)
mainTrueSheet automatically adapts to Android's edge-to-edge display mode, respecting the status bar when fully expanded.
Requirements:
- Android only.
- React Native 0.81+.
edgeToEdgeEnabled=truemust be set in yourandroid/gradle.properties.
Smooth Dimming on Android
mainStarting from version 3.4, Android supports smooth dimming using a custom dim view with real-time interpolation. The background dimming intensity is controlled by thedimmedDetentIndexprop, which allows the dimming to fade in or out smoothly based on the sheet's position relative to a threshold during dragging.Understand TrueSheet's Edge-to-Edge default behavior
mainWhen edge-to-edge mode is enabled, TrueSheet is designed to respect the system status bar. When the sheet is fully expanded, it will stop at the bottom of the status bar to ensure that the sheet's content is not obscured by system UI elements. TrueSheet handles status bar detection automatically.
<TrueSheet ref={sheet}> <View /> </TrueSheet>Collapse the sheet to the footer
mainThe footer height is included in the calculation of the
"peek"detent. When you use apeekdetent, the sheet will collapse to show theheader, thefooter, and any content designated as peeking content.const App = () => { return ( <TrueSheet detents={['peek', 0.9]} initialDetentIndex={0} footer={<SheetActions />} > <BookingDetails /> </TrueSheet> ) }