Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Build performant React Native and Expo apps with best practices for lists, animations, and navigation
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
rules/navigation-native-navigators.md
1---2title: Use Native Navigators for Navigation3impact: HIGH4impactDescription: native performance, platform-appropriate UI5tags: navigation, react-navigation, expo-router, native-stack, tabs6---78## Use Native Navigators for Navigation910Always use native navigators instead of JS-based ones. Native navigators use11platform APIs (UINavigationController on iOS, Fragment on Android) for better12performance and native behavior.1314**For stacks:** Use `@react-navigation/native-stack` or expo-router's default15stack (which uses native-stack). Avoid `@react-navigation/stack`.1617**For tabs:** Use `react-native-bottom-tabs` (native) or expo-router's native18tabs. Avoid `@react-navigation/bottom-tabs` when native feel matters.1920### Stack Navigation2122**Incorrect (JS stack navigator):**2324```tsx25import { createStackNavigator } from '@react-navigation/stack'2627const Stack = createStackNavigator()2829function App() {30return (31<Stack.Navigator>32<Stack.Screen name='Home' component={HomeScreen} />33<Stack.Screen name='Details' component={DetailsScreen} />34</Stack.Navigator>35)36}37```3839**Correct (native stack with react-navigation):**4041```tsx42import { createNativeStackNavigator } from '@react-navigation/native-stack'4344const Stack = createNativeStackNavigator()4546function App() {47return (48<Stack.Navigator>49<Stack.Screen name='Home' component={HomeScreen} />50<Stack.Screen name='Details' component={DetailsScreen} />51</Stack.Navigator>52)53}54```5556**Correct (expo-router uses native stack by default):**5758```tsx59// app/_layout.tsx60import { Stack } from 'expo-router'6162export default function Layout() {63return <Stack />64}65```6667### Tab Navigation6869**Incorrect (JS bottom tabs):**7071```tsx72import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'7374const Tab = createBottomTabNavigator()7576function App() {77return (78<Tab.Navigator>79<Tab.Screen name='Home' component={HomeScreen} />80<Tab.Screen name='Settings' component={SettingsScreen} />81</Tab.Navigator>82)83}84```8586**Correct (native bottom tabs with react-navigation):**8788```tsx89import { createNativeBottomTabNavigator } from '@bottom-tabs/react-navigation'9091const Tab = createNativeBottomTabNavigator()9293function App() {94return (95<Tab.Navigator>96<Tab.Screen97name='Home'98component={HomeScreen}99options={{100tabBarIcon: () => ({ sfSymbol: 'house' }),101}}102/>103<Tab.Screen104name='Settings'105component={SettingsScreen}106options={{107tabBarIcon: () => ({ sfSymbol: 'gear' }),108}}109/>110</Tab.Navigator>111)112}113```114115**Correct (expo-router native tabs):**116117```tsx118// app/(tabs)/_layout.tsx119import { NativeTabs } from 'expo-router/unstable-native-tabs'120121export default function TabLayout() {122return (123<NativeTabs>124<NativeTabs.Trigger name='index'>125<NativeTabs.Trigger.Label>Home</NativeTabs.Trigger.Label>126<NativeTabs.Trigger.Icon sf='house.fill' md='home' />127</NativeTabs.Trigger>128<NativeTabs.Trigger name='settings'>129<NativeTabs.Trigger.Label>Settings</NativeTabs.Trigger.Label>130<NativeTabs.Trigger.Icon sf='gear' md='settings' />131</NativeTabs.Trigger>132</NativeTabs>133)134}135```136137On iOS, native tabs automatically enable `contentInsetAdjustmentBehavior` on the138first `ScrollView` at the root of each tab screen, so content scrolls correctly139behind the translucent tab bar. If you need to disable this, use140`disableAutomaticContentInsets` on the trigger.141142### Prefer Native Header Options Over Custom Components143144**Incorrect (custom header component):**145146```tsx147<Stack.Screen148name='Profile'149component={ProfileScreen}150options={{151header: () => <CustomHeader title='Profile' />,152}}153/>154```155156**Correct (native header options):**157158```tsx159<Stack.Screen160name='Profile'161component={ProfileScreen}162options={{163title: 'Profile',164headerLargeTitleEnabled: true,165headerSearchBarOptions: {166placeholder: 'Search',167},168}}169/>170```171172Native headers support iOS large titles, search bars, blur effects, and proper173safe area handling automatically.174175### Why Native Navigators176177- **Performance**: Native transitions and gestures run on the UI thread178- **Platform behavior**: Automatic iOS large titles, Android material design179- **System integration**: Scroll-to-top on tab tap, PiP avoidance, proper safe180areas181- **Accessibility**: Platform accessibility features work automatically182183Reference:184185- [React Navigation Native Stack](https://reactnavigation.org/docs/native-stack-navigator)186- [React Native Bottom Tabs with React Navigation](https://oss.callstack.com/react-native-bottom-tabs/docs/guides/usage-with-react-navigation)187- [React Native Bottom Tabs with Expo Router](https://oss.callstack.com/react-native-bottom-tabs/docs/guides/usage-with-expo-router)188- [Expo Router Native Tabs](https://docs.expo.dev/router/advanced/native-tabs)189