TrueSheet Consumer Guide
Use this skill to produce correct, idiomatic code for apps that consume
@lodev09/react-native-true-sheet
. It covers choosing the right integration pattern, applying the public API correctly, and avoiding platform-specific pitfalls.
Quick Start
The simplest sheet: a ref, a button, and some content.
tsx
import { useRef } from 'react'
import { Button, Text, View } from 'react-native'
import { TrueSheet } from '@lodev09/react-native-true-sheet'
export function App() {
const sheet = useRef<TrueSheet>(null)
return (
<View>
<Button title="Open" onPress={() => sheet.current?.present()} />
<TrueSheet ref={sheet} detents={['auto']} cornerRadius={24} grabber>
<View style={{ padding: 16 }}>
<Text>Hello from the sheet</Text>
<Button title="Close" onPress={() => sheet.current?.dismiss()} />
</View>
</TrueSheet>
</View>
)
}
Choose the Right Control Pattern
Pick one based on where the trigger lives relative to the sheet and which platforms you target.
| Pattern | When to use | Platform |
|---|
| Ref | Trigger and sheet in the same component | All |
| Named + global methods | Trigger is far from the sheet (different screen, deep in tree) | Native only |
| + | Web support needed, or you want hook-based control | All (required on web) |
createTrueSheetNavigator()
| Sheets are part of a navigation flow | All |
| You need animated values synced to sheet position | All |
Ref-based
Already shown in Quick Start. Use
,
,
on the ref.
Named sheet with global methods (native only)
When the trigger is far from where the sheet renders:
tsx
// Somewhere in the tree
<TrueSheet name="profile" detents={['auto', 1]}>
<ProfileContent />
</TrueSheet>
// Anywhere else (native only)
await TrueSheet.present('profile')
await TrueSheet.dismiss('profile')
await TrueSheet.resize('profile', 1)
await TrueSheet.dismissAll()
Every
must be unique. Static methods don't exist on web — use the provider pattern instead.
Web control with provider
Wrap your app with
(on native this is a pass-through with zero overhead):
tsx
import { TrueSheet, TrueSheetProvider, useTrueSheet } from '@lodev09/react-native-true-sheet'
function Toolbar() {
const { present, dismiss } = useTrueSheet()
return <Button title="Open" onPress={() => present('settings')} />
}
export function App() {
return (
<TrueSheetProvider>
<Toolbar />
<TrueSheet name="settings" detents={[0.5, 1]}>
<SettingsContent />
</TrueSheet>
</TrueSheetProvider>
)
}
Navigation (React Navigation / Expo Router)
See
advanced patterns reference for full setup with
, Expo Router layouts, screen options, and
.
Reanimated
See
advanced patterns reference for
,
ReanimatedTrueSheetProvider
, and animated values (
,
,
).
Detents
Detents define the heights the sheet can snap to. You get up to 3 detents, sorted smallest to largest.
| Value | Meaning |
|---|
| Size to fit the content (iOS 16+, Android, Web) |
| – | Fraction of the screen height |
tsx
// 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]} />
The one rule you can't break: never combine
with
. Auto-sizing needs to measure the full content, but a scrollable sheet clips it — they're fundamentally incompatible. Use fractional detents for scrollable sheets.
Common Recipes
Scrollable content
tsx
<TrueSheet detents={[0.5, 1]} scrollable cornerRadius={24} grabber>
<ScrollView>
{items.map(item => <ItemRow key={item.id} item={item} />)}
</ScrollView>
</TrueSheet>
- The prop auto-detects ScrollView/FlatList up to 2 levels deep
- On iOS, scrolling to top expands to next detent — disable with
scrollableOptions={{ scrollingExpandsSheet: false }}
- On Android, nested scrolling is handled automatically
Fixed header and footer
tsx
<TrueSheet
detents={[0.5, 1]}
scrollable
header={
<View style={{ padding: 16 }}>
<Text style={{ fontSize: 18, fontWeight: 'bold' }}>Title</Text>
</View>
}
footer={<BottomActions />}
>
<ScrollView>{/* ... */}</ScrollView>
</TrueSheet>
Use the
and
props — they render in native container views, so the layout math is handled for you. Don't fake it with absolute positioning.
Non-dismissible confirmation
tsx
<TrueSheet
ref={sheet}
detents={['auto']}
dismissible={false}
draggable={false}
dimmed
grabber={false}
>
<View style={{ padding: 24 }}>
<Text>Are you sure?</Text>
<Button title="Confirm" onPress={handleConfirm} />
<Button title="Cancel" onPress={() => sheet.current?.dismiss()} />
</View>
</TrueSheet>
iOS blur background
tsx
<TrueSheet detents={['auto']} backgroundBlur="system-material">
<View style={{ padding: 16 }}>
<Text>Blurred sheet</Text>
</View>
</TrueSheet>
Fine-tune with
blurOptions={{ intensity: 80, interaction: true }}
. Blur is iOS-only.
Present on mount
tsx
<TrueSheet detents={['auto', 1]} initialDetentIndex={0} initialDetentAnimated>
<WelcomeContent />
</TrueSheet>
Dimming control
tsx
// No dimming (allows background interaction)
<TrueSheet dimmed={false} detents={['auto']} />
// Dim only above a certain detent
<TrueSheet detents={['auto', 0.7, 1]} dimmedDetentIndex={1} />
Resize programmatically
takes a
detent index, not a value:
tsx
const sheet = useRef<TrueSheet>(null)
// detents={[0.3, 0.6, 1]}
await sheet.current?.resize(2) // expands to full (index 2)
Rules That Save Debugging Time
- Max 3 detents, sorted smallest → largest.
- Never + — they're incompatible.
- takes an index, not a fraction. means "go to the second detent."
- Sheet names must be unique across your entire app.
- Static methods are native-only — use on web.
- Don't use on TextInputs inside sheets. Focus in instead:
tsx
<TrueSheet onDidPresent={() => inputRef.current?.focus()}>
- Use (not ) inside on Android.
- Dismiss sheets before closing Modals on iOS — React Native has a bug where dismissing a Modal while a sheet is visible causes a blank screen.
- Use / props for fixed chrome — don't reach for absolute positioning.
- Liquid Glass is automatic on iOS 26+. Set to disable it per-sheet, or add
UIDesignRequiresCompatibility
to Info.plist to disable app-wide.
Platform Differences at a Glance
| Feature | iOS | Android | Web |
|---|
| detent | iOS 16+ | Yes | Yes |
| Yes | No | No |
| Liquid Glass | iOS 26+ | No | No |
| Static global methods | Yes | Yes | No (use provider) |
| Yes | Yes | No (use ) |
| / side sheets | System-controlled margins | prop | prop |
| (iPad) | iOS 17+ | N/A | N/A |
| mode | No | No | Yes |
| N/A | N/A | / / |
| Edge-to-edge | N/A | Auto-detected | N/A |
| Keyboard handling | Built-in | Built-in | N/A |
Events
The most commonly used events:
| Event | When it fires | Payload |
|---|
| Content is mounted and ready | — |
| Sheet finished presenting | { index, position, detent }
|
| Sheet finished dismissing | — |
| User dragged or changed the detent | { index, position, detent }
|
| Continuous position updates during drag/animation | { index, position, detent, realtime }
|
For the full event list (drag events, focus/blur events, will/did lifecycle pairs,
), see the
API reference.
Methods
On a ref:
present(index?, animated?)
— show the sheet
- — hide the sheet and all its children
- — hide only sheets stacked on top
- — snap to a detent by index
Global (native only):
TrueSheet.present(name, index?, animated?)
TrueSheet.dismiss(name, animated?)
TrueSheet.dismissStack(name, animated?)
TrueSheet.resize(name, index)
TrueSheet.dismissAll(animated?)
Web hook:
tsx
const { present, dismiss, dismissStack, resize, dismissAll } = useTrueSheet()
Stacking Sheets
Present a new sheet while another is visible and the first one hides automatically. Dismiss the top sheet and the previous one comes back. This is built-in — no extra config needed.
- cascades: it dismisses the current sheet plus everything stacked on top
- dismisses only the sheets on top, keeping the current one visible
- Use / to react to a sheet gaining or losing the top position
Deep-Dive References
When you need the full picture, load these reference files:
| Reference | What's inside |
|---|
| Configuration | Every prop with type, default, platform support, and notes |
| API | Complete events and methods reference with payload types |
| Advanced Patterns | Navigation, Reanimated, Web, Side sheets, Liquid Glass, Jest mocking, Migration v2→v3 |
| Troubleshooting | Common issues and fixes by platform |