Skip to main content

React Navigation

TrueSheet integrates with React Navigation out of the box. It just works!

navigation

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

OptionTypeDescription
detentIndexnumberThe detent index to present at. Defaults to 0.
reanimatedbooleanEnable worklet-based position events for this screen.
positionChangeHandlerfunctionA 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);
},
}}
/>
note

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'),
}}
>
EventDescription
sheetWillPresentSheet is about to present
sheetDidPresentSheet finished presenting
sheetWillDismissSheet is about to dismiss
sheetDidDismissSheet finished dismissing
sheetDetentChangeDetent changed
sheetDragBeginUser started dragging
sheetDragChangeUser is dragging
sheetDragEndUser stopped dragging
sheetPositionChangePosition 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>
);
}

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>