- Skill ID
- lodev09/react-native-true-sheet/truesheet-usage
- Publisher
- lodev09
- Repository
- react-native-true-sheet
- Installs
- 0
- Files
- 5
- Synced
- Sep 23, 2026
Open any RiverX project, open the Skills panel in the chat, and search for this identifier. The files are fetched from the source repository at install time.
lodev09/react-native-true-sheet/truesheet-usageInstalls these files- SKILL.md
- references/advanced-patterns.md
- references/api.md
- references/configuration.md
- references/troubleshooting.md
What this skill tells the agent
TrueSheet Consumer Guide
Use this skill to produce correct, idiomatic code for apps that consume @lodev09/react-native-true-sheet (v4). It covers choosing the right integration pattern, applying the public API correctly, and avoiding platform-specific pitfalls.
Requires React Native >= 0.82 (Expo SDK 55+) with the New Architecture.
Quick Start
The simplest sheet: a ref, a button, and some content.
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>
)
}Content Layout (v4)
The sheet's content lays out naturally — it wraps its children's height like a regular view or a React Navigation screen. It does not fill the sheet by default. Consequences:
- A
flex: 1child collapses to zero height unless the content is filled. - To fill the sheet's visible height per detent (spacers, centered content, bounded scroll views), pass
flex: 1via the sheet'sstyleprop:
<TrueSheet style={{ flex: 1 }} detents={[0.5, 1]}>
<View style={{ flex: 1, justifyContent: 'center' }}>
<Text>Centered</Text>
</View>
</TrueSheet>The content is sized to the sheet's visible height per detent and tracks it in realtime while dragging, so flex layouts follow the sheet's edge frame-by-frame.
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 |
| `TrueSheetProvider` + `useTrueSheet()` | Web support needed, or you want hook-based control | All (required on web) |
| `createTrueSheetNavigator()` / Expo Router `Sheet` | Sheets are part of a navigation flow | All |
| `ReanimatedTrueSheet` | You need animated values synced to sheet position | All |
Ref-based
Already shown in Quick Start. Use present(), dismiss(), resize(index) on the ref.
Named sheet with global methods (native only)
When the trigger is far from where the sheet renders:
// 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 name must be unique. Static methods don't exist on web — use the provider pattern instead.
Web control with provider
Wrap your app with TrueSheetProvider (on native this is a pass-through with zero overhead). Web also needs @radix-ui/react-dialog and @radix-ui/react-presence installed (optional peer deps):
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 createTrueSheetNavigator, the static API (createTrueSheetScreen), the Expo Router Sheet layout from /navigation/expo-router, screen options (including scrollableRef via setOptions), multi-step flows as stacked sheet screens, and useTrueSheetNavigation.
Reanimated
See advanced patterns reference for ReanimatedTrueSheet, ReanimatedTrueSheetProvider, and animated values (animatedPosition, animatedIndex, animatedDetent).
Detents
Detents define the heights the sheet can snap to. You get up to 3 detents, sorted smallest to largest. Default: [0.5, 1].
| Value | Meaning |
|---|---|
'auto' | Size to fit the content (iOS 16+, Android, Web). Works with scrollables — sizes to the scroll content and resizes as it grows/shrinks |
'peek' | Collapsed height from the measured header + absolute footer + content through a TrueSheetPeek marker (iOS 16+, Android, Web). Falls back to 150 when none provided |
0 – 1 | Fraction of the screen height |
// Content-sized sheet
<TrueSheet detents={['auto']} />
// Half and full screen
<TrueSheet detents={[0.5, 1]} />
// Collapsed summary that expands to near-full
<TrueSheet detents={['peek', 0.9]} />Common Recipes
Scrollable content
Point the sheet at your scroll view with scrollableRef. For fixed detents, bound the scroll view with flex: 1 on the sheet's style:
const scrollableRef = useRef<ScrollView>(null)
<TrueSheet scrollableRef={scrollableRef} style={{ flex: 1 }} detents={[0.5, 1]} grabber>
<ScrollView ref={scrollableRef}>
{items.map(item => <ItemRow key={item.id} item={item} />)}
</ScrollView>
</TrueSheet>- With
detents={['auto']}noflex: 1is needed — the sheet sizes to the scroll content and bounds the viewport automatically - The scroll view doesn't have to be a direct child — a wrapping
Viewworks - Keyboard handling, nested scrolling (Android), and the bottom safe-area inset are wired automatically
- Scrolling to top expands to the next detent — disable with
scrollableOptions={{ scrollingExpandsSheet: false }}(YouTube-style comments) - The bottom safe-area inset (and an absolute footer's height) is applied natively while content can scroll — don't add manual padding, or opt out with
scrollableOptions={{ contentInsetAdjustment: 'never' }}('safe-area'/'footer'keep one of the two)
Fixed header and footer
<TrueSheet
detents={[0.5, 1]}
scrollableRef={scrollableRef}
style={{ flex: 1 }}
header={
<View style={{ padding: 16 }}>
<Text style={{ fontSize: 18, fontWeight: 'bold' }}>Title</Text>
</View>
}
footer={<BottomActions />}
>
<ScrollView ref={scrollableRef}>{/* ... */}</ScrollView>
</TrueSheet>- Use the
headerandfooterprops — they render in native container views, so the layout math is handled for you. Don't fake it with absolute positioning. - Both are relative by default: header takes space above the content, footer below it, and both count toward the
'auto'detent. - The footer absorbs the bottom safe-area inset natively — remove manual
paddingBottom: insets.bottomfrom footer content or it doubles up. Set the footer background viafooterStyleso it fills the inset. - A relative footer is laid out below the content — with fixed detents, bound the content with
flex: 1(sheetstyle) so the footer stays visible. - To float them over the content instead (excluded from
'auto'height), useheaderOptions={{ position: 'absolute' }}/footerOptions={{ position: 'absolute' }}. An absolute footer rises above the keyboard (opt out withavoidKeyboard: false); a relative one stays in the layout flow behind it. - An absolute footer covers the bottom of a plugged scrollable — the scroll content is padded by the measured footer height automatically (default
contentInsetAdjustment: 'automatic') and the footer counts toward'auto', so don't hard-codepaddingBottom: FOOTER_HEIGHT. Use'safe-area'to let content scroll under the footer.
Use this skill in a project
Open any RiverX project, search the Skills panel for lodev09/react-native-true-sheet/truesheet-usage, and install it in one click.
