lodev09/react-native-true-sheet/truesheet-usage

Trust tier 2
  • community
Source check pending

Checking that the SKILL.md file is still in the repository.

Consumer-side guide for integrating @lodev09/react-native-true-sheet into a React Native app. Use this skill whenever the user wants to add, configure, control, or debug a bottom sheet using TrueSheet — including ref-based sheets, named global sheets, web support with TrueSheetProvider/useTrueSheet, React Navigation or Expo Router sheet flows, Reanimated-driven animations, scrolling content, stacking, headers/footers, detents, peeking, side sheets, keyboard handling, dimming, backgrounds, liquid glass, and Jest testing. Also use when the user is migrating from v3 to v4, troubleshooting layout or gesture issues, or asking about any TrueSheet prop, event, or method — even if they don't mention "TrueSheet" by name but describe a bottom sheet in a React Native context.

Install

npx vskill@latest install lodev09/react-native-true-sheet/truesheet-usage

SKILL.md

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.

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>
)
}

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: 1 child 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: 1 via the sheet's style prop:
tsx
<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.

PatternWhen to usePlatform
RefTrigger and sheet in the same componentAll
Named + global methodsTrigger is far from the sheet (different screen, deep in tree)Native only
`TrueSheetProvider` + `useTrueSheet()`Web support needed, or you want hook-based controlAll (required on web)
`createTrueSheetNavigator()` / Expo Router `Sheet`Sheets are part of a navigation flowAll
`ReanimatedTrueSheet`You need animated values synced to sheet positionAll
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:

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 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):

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 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].

ValueMeaning
'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 – 1Fraction of the screen height
tsx
// 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:

tsx
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']} no flex: 1 is needed — the sheet sizes to the scroll content and bounds the viewport automatically
  • Exception: lists that wrap their scroll view in flex: 1 (e.g. FlashList) render blank with 'auto' unless the sheet has style={{ flex: 1 }} — the sheet still sizes to the list
  • The scroll view doesn't have to be a direct child — a wrapping View works
  • 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
tsx
<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 header and footer props — 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.bottom from footer content or it doubles up. Set the footer background via footerStyle so it fills the inset.
  • A relative footer is laid out below the content — with fixed detents, bound the content with flex: 1 (sheet style) so the footer stays visible.
  • To float them over the content instead (excluded from 'auto' height), use headerOptions={{ position: 'absolute' }} / footerOptions={{ position: 'absolute' }}. An absolute footer rises above the keyboard (opt out with avoidKeyboard: 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-code paddingBottom: FOOTER_HEIGHT. Use 'safe-area' to let content scroll under the footer.
  • With avoidKeyboard: false, only the footer portion above the keyboard adds scroll padding and contributes to the expanded 'auto' height. Footer inset adjustment must be enabled.
Peeking (map-style collapsed sheet)
tsx
import { TrueSheet, TrueSheetPeek } from '@lodev09/react-native-true-sheet'
<TrueSheet detents={['peek', 0.9]} initialDetentIndex={0} header={<Summary />}>
<TrueSheetPeek>
<QuickActions />
</TrueSheetPeek>
<FullDetails />
</TrueSheet>

Collapsed, the sheet shows everything from the top through the bottom edge of TrueSheetPeek; content below stays hidden until expanded. An empty <TrueSheetPeek /> works as a fold marker between existing views. One peek component per sheet. Absolute footers count toward the peek height; relative footers don't (pushed off-screen at peek).

Toasts and dialogs above sheets
tsx
import { TrueSheet, TrueSheetOverlay } from '@lodev09/react-native-true-sheet'
<TrueSheetOverlay>
{toast && <Toast message={toast} />}
</TrueSheetOverlay>

Renders children in a native layer above every presented sheet. Show/hide by conditionally rendering children. Fills the window; touches that miss the children pass through. Never use RN Modal/FullWindowOverlay for this anymore. Don't put react-native-screens containers (native stack, tabs, drawer) inside it — Android crashes.

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>
Confirm before dismiss (unsaved changes)
tsx
<TrueSheet
ref={sheet}
dismissible={!isDirty}
onDismissAttempt={() =>
Alert.alert('Discard changes?', undefined, [
{ text: 'Keep editing', style: 'cancel' },
{ text: 'Discard', style: 'destructive', onPress: () => sheet.current?.dismiss() },
])
}
>
{/* form */}
</TrueSheet>

Don't try to block dismissal from a callback — native can't wait on JS. Toggle dismissible from state; blocked drags and back/escape fire onDismissAttempt (dim taps don't), then dismiss() programmatically.

Backgrounds
tsx
<TrueSheet backgroundBlur="system-material" /> {/* iOS-only blur, wins over backgroundColor */}
<TrueSheet backgroundStyle={{ backgroundColor: 'rgba(0, 122, 255, 0.25)' }} /> {/* tint glass/blur */}
<TrueSheet backgroundStyle={{ backgroundColor: '#18202b' }} /> {/* exact opaque color */}
<TrueSheet backgroundColor="transparent" /> {/* clear, no glass on supported iOS 26.1+ */}
<TrueSheet background={<BlurView intensity={40} style={StyleSheet.absoluteFill} />} /> {/* custom node */}
<TrueSheet detentBackgrounds={[null, { blur: 'system-material' }, '#18202b']} /> {/* per detent */}

backgroundStyle.backgroundColor paints over glass or blur. background renders any node above the sheet effects; the wrapper ignores touches. blurOptions is removed in v4. For custom intensity, use a third-party blur in background. See Backgrounds for layers, per-detent transitions, and iOS fallbacks.

Present on mount
tsx
<TrueSheet detents={['auto', 1]} initialDetentIndex={0} initialDetentAnimated>
<WelcomeContent />
</TrueSheet>
Pre-mount content for 'auto' detents

Content mounts on first present() by default. If it settles asynchronously (data fetch, images), 'auto' measures too early. Set lazy={false} to mount content up front without presenting, then present once ready (native only):

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
2,066
Repository forks
108
7-day trend score
38

Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/lodev09/react-native-true-sheet/truesheet-usage/badge)](https://verified-skill.com/skills/lodev09/react-native-true-sheet/truesheet-usage)

Something wrong with this skill or its page? Tell us.