React Navigation
TrueSheet integrates with React Navigation out of the box. It just works!
How?
You can use the Sheet Navigator to present screens as sheets, or simply navigate from within sheets using your existing navigation setup.
Sheet Navigator
TrueSheet provides a custom navigator for React Navigation. The first screen (or initialRouteName) is the base content, while other screens are presented as sheets.
npm install @react-navigation/native
Basic Usage
import { NavigationContainer } from '@react-navigation/native';
import {
createTrueSheetNavigator,
useTrueSheetNavigation,
} from '@lodev09/react-native-true-sheet/navigation';
const Sheet = createTrueSheetNavigator();
function App() {
return (
<NavigationContainer>
<Sheet.Navigator>
{/* Base screen (first screen is the default) */}
<Sheet.Screen name="Main" component={MainScreen} />
{/* Sheet screens */}
<Sheet.Screen
name="Details"
component={DetailsSheet}
options={{ detents: ['auto', 1], cornerRadius: 16 }}
/>
</Sheet.Navigator>
</NavigationContainer>
);
}
Wrapping Existing Navigation
Wrap your root navigator to present sheets from anywhere:
const Stack = createNativeStackNavigator();
const Sheet = createTrueSheetNavigator();
function RootStack() {
return (
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Profile" component={ProfileScreen} />
</Stack.Navigator>
);
}
function App() {
return (
<NavigationContainer>
<Sheet.Navigator>
<Sheet.Screen name="Root" component={RootStack} />
<Sheet.Screen
name="Details"
component={DetailsSheet}
options={{ detents: ['auto', 1], cornerRadius: 16 }}
/>
</Sheet.Navigator>
</NavigationContainer>
);
}
Navigation & Resizing
function DetailsSheet() {
const navigation = useTrueSheetNavigation();
return (
<View>
<Button title="Expand" onPress={() => navigation.resize(1)} />
<Button title="Close" onPress={() => navigation.goBack()} />
</View>
);
}
Screen Options
All TrueSheet props are available as screen options, plus the following navigation-specific options:
| Option | Type | Description |
|---|---|---|
detentIndex | number | The detent index to present at. Defaults to 0. |
reanimated | boolean | Enable worklet-based position events for this screen. |
positionChangeHandler | function | A callback that receives position change events. When reanimated is enabled, this must be a worklet function. |
Reanimated Integration
Enable worklet-based position events for smooth UI thread animations:
// In your navigator
<Sheet.Screen
name="Details"
component={DetailsSheet}
options={{
reanimated: true,
positionChangeHandler: (payload) => {
'worklet';
// Access payload.position, payload.detentIndex, etc.
console.log(payload.position);
},
}}
/>
When reanimated: true is set, react-native-reanimated must be installed and positionChangeHandler must be a worklet function. The integration is lazy-loaded, so screens without reanimated: true don't require reanimated.
Screen Listeners
<Sheet.Navigator
screenListeners={{
sheetDidPresent: (e) => console.log('Presented:', e.data.index),
sheetDidDismiss: () => console.log('Dismissed'),
}}
>
| Event | Description |
|---|---|
sheetWillPresent | Sheet is about to present |
sheetDidPresent | Sheet finished presenting |
sheetWillDismiss | Sheet is about to dismiss |
sheetDidDismiss | Sheet finished dismissing |
sheetDetentChange | Detent changed |
sheetDragBegin | User started dragging |
sheetDragChange | User is dragging |
sheetDragEnd | User stopped dragging |
sheetPositionChange | Position changed |
Expo Router
app/
├── _layout.tsx # TrueSheet navigator
├── index.tsx # Base content
└── details.tsx # Sheet screen
// app/_layout.tsx
import { withLayoutContext } from 'expo-router';
import {
createTrueSheetNavigator,
type TrueSheetNavigationEventMap,
type TrueSheetNavigationOptions,
type TrueSheetNavigationState,
} from '@lodev09/react-native-true-sheet/navigation';
import type { ParamListBase } from '@react-navigation/native';
const { Navigator } = createTrueSheetNavigator();
const Sheet = withLayoutContext<
TrueSheetNavigationOptions,
typeof Navigator,
TrueSheetNavigationState<ParamListBase>,
TrueSheetNavigationEventMap
>(Navigator);
export default function SheetLayout() {
return (
<Sheet>
<Sheet.Screen name="index" />
<Sheet.Screen
name="details"
options={{
detents: ['auto', 1],
cornerRadius: 16,
}}
/>
</Sheet>
);
}
Navigating from Sheets
Requires a patch to react-native-screens. See PR #3415.
Navigate directly from sheets - they remain visible when presenting modals on top.
// Navigate directly - no need to dismiss first!
navigation.navigate('SomeScreen')
Focus Events
Respond when sheets lose/regain focus due to modals:
<TrueSheet
onDidBlur={() => console.log('Lost focus')}
onDidFocus={() => console.log('Regained focus')}
>
Presenting on Screen Focus
When using useFocusEffect, delay presentation to avoid iOS issues:
useFocusEffect(
useCallback(() => {
requestAnimationFrame(() => {
sheet.current?.present()
})
}, [])
)
Present During Mount
When using initialDetentIndex with animation, the sheet may behave unexpectedly during screen transitions.
Solution 1: Disable animation
<TrueSheet initialDetentIndex={0} initialDetentAnimated={false}>
Solution 2: Wait for transition
const [ready, setReady] = useState(false)
useEffect(() => {
const unsubscribe = navigation.addListener("transitionEnd", ({ data }) => {
if (!data.closing) setReady(true)
})
return unsubscribe
}, [])
if (!ready) return null
return <TrueSheet initialDetentIndex={0}>{/* ... */}</TrueSheet>