createProps custom props typing
Declare props returned by createProps in the third type argument to NavigatorContentProps so NavigatorContent receives them typed. createProps receives raw Expo Router state and dispatch which are internal and may have breaking changes, so prefer using standard state, actions, and emitter when possible.
Standard navigator API for library authors
NavigatorContent implements the standard navigator contract defined by the standard-navigation package. The state, descriptors, actions, and emitter are framework-agnostic and the same whether created by unstable_createStandardRouterNavigator or by calling createStandardNavigator directly.
unstable_createStandardRouterNavigator API overview
unstable_createStandardRouterNavigator turns a content component into a navigator for use as a layout. It takes two required arguments: NavigatorContent (a component that renders the navigator's UI, receiving state, descriptors, actions, and emitter) and router (StackRouter or TabRouter imported from expo-router). The returned navigator has a .Screen child for declaring screens, usable in _layout files like built-in layouts.
NavigatorContent props table
NavigatorContent receives the following properties: (1) state — the current navigation state with index and routes, each route having key, name, params, and href; (2) descriptors — a map keyed by route.key with each descriptor exposing the screen's resolved options and a render() function; (3) actions — functions to change navigation state: navigate(name, params?) and back(); (4) emitter — an object with emit() method for sending events to screens.
Typed events in NavigatorContent
Declare custom events in the second type argument to NavigatorContentProps. Each key is an event name with a value describing the event's data and canPreventDefault boolean. emitter.emit is then typed against that map, rejecting unknown event names and mismatched payloads. unstable_createStandardRouterNavigator infers the event map from the component, so you do not pass it at the call site.
Custom navigator options parameter
Both unstable_createStandardRouterNavigator and unstable_integrateWithRouter accept an optional options object as third argument with two fields: (1) useOnlyUserDefinedScreens (boolean, default false) — when true, only screens declared with <Navigator.Screen> are rendered and filesystem routes are ignored; (2) createProps (function) — derives extra props for NavigatorContent from router state, useful for router-specific information not in standard state and actions.
createStandardNavigator for reusable navigators
Library authors should call createStandardNavigator (from standard-navigation package) directly to create a framework-agnostic navigator. It takes two type arguments: the per-screen options type and the event map type. The resulting navigator depends only on the standard contract and runs on Expo Router, React Navigation, or any other framework implementing it.
unstable_integrateWithRouter for Expo Router integration
Wire a standard navigator into Expo Router using unstable_integrateWithRouter, passing the navigator and a router (StackRouter or TabRouter). The returned component works like unstable_createStandardRouterNavigator output, including the .Screen child and same options parameter.
Library entry points structure for multi-framework navigators
Keep navigator content and standard navigator framework-agnostic in one place, then expose one entry point per framework: TabsContent.tsx (navigator UI implementing standard navigator API), index.ts (root framework-agnostic export), react-navigation.ts (React Navigation integration), expo-router.ts (Expo Router integration). Map each to subpath exports in package.json.
Package.json subpath exports for framework-specific integrations
Map framework-specific entry points using Node.js subpath exports in package.json. Example structure: '.' points to root (framework-agnostic), './react-navigation' points to React Navigation build output, './expo-router' points to Expo Router build output. Each subpath should have types and default fields pointing to .d.ts and .js build files respectively.
Minimal tab navigator example
Example creating a custom tab navigator with unstable_createStandardRouterNavigator:
```tsx
import {
unstable_createStandardRouterNavigator,
TabRouter,
type NavigatorContentProps,
} from 'expo-router';
import { Pressable, Text, View } from 'react-native';
type TabsContentProps = NavigatorContentProps<{ title?: string }>;
function TabsContent({ state, descriptors, actions }: TabsContentProps) {
const focusedRoute = state.routes[state.index];
return (
<View style={{ flex: 1 }}>
<View style={{ flex: 1 }}>{descriptors[focusedRoute.key].render()}</View>
<View style={{ flexDirection: 'row' }}>
{state.routes.map(route => (
<Pressable
key={route.key}
style={{ flex: 1, padding: 16 }}
onPress={() => actions.navigate(route.name)}>
<Text>{descriptors[route.key].options.title ?? route.name}</Text>
</Pressable>
))}
</View>
</View>
);
}
export const Tabs = unstable_createStandardRouterNavigator(TabsContent, TabRouter);
```
Custom navigator in _layout file
Use a custom navigator in a _layout file like built-in layouts:
```tsx
import { Tabs } from '../components/Tabs';
export default function Layout() {
return (
<Tabs>
<Tabs.Screen name="index" options={{ title: 'Home' }} />
<Tabs.Screen name="settings" options={{ title: 'Settings' }} />
</Tabs>
);
}
```
Typed events example in NavigatorContent
Example declaring typed events in NavigatorContent:
```tsx
type TabsContentProps = NavigatorContentProps<
{ title?: string },
{ tabPress: { data: undefined; canPreventDefault: true } }
>;
function TabsContent({ emitter }: TabsContentProps) {
emitter.emit({ type: 'tabPress', canPreventDefault: true });
// ...
}
```
createProps example with custom props
Example using createProps to add custom props to NavigatorContent:
```tsx
export const Tabs = unstable_createStandardRouterNavigator(TabsContent, TabRouter, {
useOnlyUserDefinedScreens: true,
createProps: ({ state, dispatch }) => ({
activeRouteKey: state.routes[state.index].key,
preload: (name: string) => dispatch({ type: 'PRELOAD', payload: { name } }),
}),
});
type TabsContentProps = NavigatorContentProps<
{ title?: string },
Record<string, never>,
{ activeRouteKey: string; preload: (name: string) => void }
>;
function TabsContent({ activeRouteKey, preload }: TabsContentProps) {
// ...
}
```
createStandardNavigator library example
Example creating a framework-agnostic navigator for library distribution:
```tsx
import { createStandardNavigator } from 'standard-navigation';
import { TabsContent } from './TabsContent';
export const navigator = createStandardNavigator<
{ title?: string },
{ tabPress: { data: undefined; canPreventDefault: true } }
>(TabsContent);
```
unstable_integrateWithRouter library example
Example integrating a standard navigator with Expo Router in a library:
```tsx
import { unstable_integrateWithRouter, TabRouter } from 'expo-router';
import { navigator } from './index';
export const Tabs = unstable_integrateWithRouter(navigator, TabRouter);
```
Custom navigator API status and availability
The custom navigator API described in this documentation is in alpha status and available in SDK 56 and later. The API is subject to breaking changes.
Protected routes with custom navigators
Protected is available for custom navigators using the withLayoutContext hook.
Prefetch behavior with custom navigators
Custom navigators may implement prefetching differently or not support it at all. Expo Router navigators will render the target screen off-screen to enable preloading when prefetch is set.
Port custom navigators using withLayoutContext
If your project has a custom navigator, you can port it to Expo Router by using the withLayoutContext function. Example: export const CustomNavigator = withLayoutContext(createCustomNavigator().Navigator);
Rewrite custom navigators using Navigator component and useNavigationBuilder
To rewrite a custom navigator in Expo Router, use the Navigator component, which wraps the useNavigationBuilder hook from React Navigation. The return value of useNavigationBuilder can be accessed with Navigator.useContext() from inside the <Navigator /> component. Properties are passed using props of the <Navigator /> component, including initialRouteName, screenOptions, and router. All children of <Navigator /> are rendered as-is.
Navigator.useContext and Navigator.Slot for custom navigators
Navigator.useContext allows access to the React Navigation state, navigation, descriptors, and router for the custom navigator. Navigator.Slot is a React component used to render the currently selected route and can only be rendered inside a <Navigator /> component.