Pulled ALL skills from 15 source repositories: - anthropics/skills: 16 (docs, design, MCP, testing) - obra/superpowers: 14 (TDD, debugging, agents, planning) - coreyhaines31/marketingskills: 25 (marketing, CRO, SEO, growth) - better-auth/skills: 5 (auth patterns) - vercel-labs/agent-skills: 5 (React, design, Vercel) - antfu/skills: 16 (Vue, Vite, Vitest, pnpm, Turborepo) - Plus 13 individual skills from various repos Mosaic Stack is not limited to coding — the Orchestrator and subagents serve coding, business, design, marketing, writing, logistics, analysis, and more. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
189 lines
4.8 KiB
Markdown
189 lines
4.8 KiB
Markdown
---
|
|
title: Use Native Navigators for Navigation
|
|
impact: HIGH
|
|
impactDescription: native performance, platform-appropriate UI
|
|
tags: navigation, react-navigation, expo-router, native-stack, tabs
|
|
---
|
|
|
|
## Use Native Navigators for Navigation
|
|
|
|
Always use native navigators instead of JS-based ones. Native navigators use
|
|
platform APIs (UINavigationController on iOS, Fragment on Android) for better
|
|
performance and native behavior.
|
|
|
|
**For stacks:** Use `@react-navigation/native-stack` or expo-router's default
|
|
stack (which uses native-stack). Avoid `@react-navigation/stack`.
|
|
|
|
**For tabs:** Use `react-native-bottom-tabs` (native) or expo-router's native
|
|
tabs. Avoid `@react-navigation/bottom-tabs` when native feel matters.
|
|
|
|
### Stack Navigation
|
|
|
|
**Incorrect (JS stack navigator):**
|
|
|
|
```tsx
|
|
import { createStackNavigator } from '@react-navigation/stack'
|
|
|
|
const Stack = createStackNavigator()
|
|
|
|
function App() {
|
|
return (
|
|
<Stack.Navigator>
|
|
<Stack.Screen name='Home' component={HomeScreen} />
|
|
<Stack.Screen name='Details' component={DetailsScreen} />
|
|
</Stack.Navigator>
|
|
)
|
|
}
|
|
```
|
|
|
|
**Correct (native stack with react-navigation):**
|
|
|
|
```tsx
|
|
import { createNativeStackNavigator } from '@react-navigation/native-stack'
|
|
|
|
const Stack = createNativeStackNavigator()
|
|
|
|
function App() {
|
|
return (
|
|
<Stack.Navigator>
|
|
<Stack.Screen name='Home' component={HomeScreen} />
|
|
<Stack.Screen name='Details' component={DetailsScreen} />
|
|
</Stack.Navigator>
|
|
)
|
|
}
|
|
```
|
|
|
|
**Correct (expo-router uses native stack by default):**
|
|
|
|
```tsx
|
|
// app/_layout.tsx
|
|
import { Stack } from 'expo-router'
|
|
|
|
export default function Layout() {
|
|
return <Stack />
|
|
}
|
|
```
|
|
|
|
### Tab Navigation
|
|
|
|
**Incorrect (JS bottom tabs):**
|
|
|
|
```tsx
|
|
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'
|
|
|
|
const Tab = createBottomTabNavigator()
|
|
|
|
function App() {
|
|
return (
|
|
<Tab.Navigator>
|
|
<Tab.Screen name='Home' component={HomeScreen} />
|
|
<Tab.Screen name='Settings' component={SettingsScreen} />
|
|
</Tab.Navigator>
|
|
)
|
|
}
|
|
```
|
|
|
|
**Correct (native bottom tabs with react-navigation):**
|
|
|
|
```tsx
|
|
import { createNativeBottomTabNavigator } from '@bottom-tabs/react-navigation'
|
|
|
|
const Tab = createNativeBottomTabNavigator()
|
|
|
|
function App() {
|
|
return (
|
|
<Tab.Navigator>
|
|
<Tab.Screen
|
|
name='Home'
|
|
component={HomeScreen}
|
|
options={{
|
|
tabBarIcon: () => ({ sfSymbol: 'house' }),
|
|
}}
|
|
/>
|
|
<Tab.Screen
|
|
name='Settings'
|
|
component={SettingsScreen}
|
|
options={{
|
|
tabBarIcon: () => ({ sfSymbol: 'gear' }),
|
|
}}
|
|
/>
|
|
</Tab.Navigator>
|
|
)
|
|
}
|
|
```
|
|
|
|
**Correct (expo-router native tabs):**
|
|
|
|
```tsx
|
|
// app/(tabs)/_layout.tsx
|
|
import { NativeTabs } from 'expo-router/unstable-native-tabs'
|
|
|
|
export default function TabLayout() {
|
|
return (
|
|
<NativeTabs>
|
|
<NativeTabs.Trigger name='index'>
|
|
<NativeTabs.Trigger.Label>Home</NativeTabs.Trigger.Label>
|
|
<NativeTabs.Trigger.Icon sf='house.fill' md='home' />
|
|
</NativeTabs.Trigger>
|
|
<NativeTabs.Trigger name='settings'>
|
|
<NativeTabs.Trigger.Label>Settings</NativeTabs.Trigger.Label>
|
|
<NativeTabs.Trigger.Icon sf='gear' md='settings' />
|
|
</NativeTabs.Trigger>
|
|
</NativeTabs>
|
|
)
|
|
}
|
|
```
|
|
|
|
On iOS, native tabs automatically enable `contentInsetAdjustmentBehavior` on the
|
|
first `ScrollView` at the root of each tab screen, so content scrolls correctly
|
|
behind the translucent tab bar. If you need to disable this, use
|
|
`disableAutomaticContentInsets` on the trigger.
|
|
|
|
### Prefer Native Header Options Over Custom Components
|
|
|
|
**Incorrect (custom header component):**
|
|
|
|
```tsx
|
|
<Stack.Screen
|
|
name='Profile'
|
|
component={ProfileScreen}
|
|
options={{
|
|
header: () => <CustomHeader title='Profile' />,
|
|
}}
|
|
/>
|
|
```
|
|
|
|
**Correct (native header options):**
|
|
|
|
```tsx
|
|
<Stack.Screen
|
|
name='Profile'
|
|
component={ProfileScreen}
|
|
options={{
|
|
title: 'Profile',
|
|
headerLargeTitleEnabled: true,
|
|
headerSearchBarOptions: {
|
|
placeholder: 'Search',
|
|
},
|
|
}}
|
|
/>
|
|
```
|
|
|
|
Native headers support iOS large titles, search bars, blur effects, and proper
|
|
safe area handling automatically.
|
|
|
|
### Why Native Navigators
|
|
|
|
- **Performance**: Native transitions and gestures run on the UI thread
|
|
- **Platform behavior**: Automatic iOS large titles, Android material design
|
|
- **System integration**: Scroll-to-top on tab tap, PiP avoidance, proper safe
|
|
areas
|
|
- **Accessibility**: Platform accessibility features work automatically
|
|
|
|
Reference:
|
|
|
|
- [React Navigation Native Stack](https://reactnavigation.org/docs/native-stack-navigator)
|
|
- [React Native Bottom Tabs with React Navigation](https://oss.callstack.com/react-native-bottom-tabs/docs/guides/usage-with-react-navigation)
|
|
- [React Native Bottom Tabs with Expo Router](https://oss.callstack.com/react-native-bottom-tabs/docs/guides/usage-with-expo-router)
|
|
- [Expo Router Native Tabs](https://docs.expo.dev/router/advanced/native-tabs)
|