rn-navigation
Expo Router navigation patterns for React Native. Use when implementing navigation, routing, deep links, tab bars, modals, or handling navigation state in Expo apps.
Best use case
rn-navigation is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Expo Router navigation patterns for React Native. Use when implementing navigation, routing, deep links, tab bars, modals, or handling navigation state in Expo apps.
Teams using rn-navigation should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/rn-navigation/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How rn-navigation Compares
| Feature / Agent | rn-navigation | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Expo Router navigation patterns for React Native. Use when implementing navigation, routing, deep links, tab bars, modals, or handling navigation state in Expo apps.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# React Native Navigation (Expo Router)
## File-Based Routing Fundamentals
Expo Router uses file-system based routing. Files in `app/` become routes.
### Route Structure
```
app/
├── _layout.tsx # Root layout (providers, global UI)
├── index.tsx # "/" route
├── (tabs)/ # Tab group (parentheses = layout group)
│ ├── _layout.tsx # Tab bar configuration
│ ├── home.tsx # "/home" tab
│ └── profile.tsx # "/profile" tab
├── (auth)/ # Auth flow group
│ ├── _layout.tsx # Auth-specific layout
│ ├── login.tsx # "/login"
│ └── register.tsx # "/register"
├── settings/
│ ├── index.tsx # "/settings"
│ └── [id].tsx # "/settings/123" (dynamic)
└── [...missing].tsx # Catch-all 404
```
### Layout Groups `(groupName)`
Parentheses create layout groups - they affect layout hierarchy but not URL:
```typescript
// app/(tabs)/_layout.tsx
import { Tabs } from 'expo-router';
export default function TabLayout() {
return (
<Tabs>
<Tabs.Screen
name="home"
options={{
title: 'Home',
tabBarIcon: ({ color }) => <HomeIcon color={color} />,
}}
/>
<Tabs.Screen
name="profile"
options={{ title: 'Profile' }}
/>
</Tabs>
);
}
```
### Dynamic Routes `[param]`
```typescript
// app/player/[id].tsx
import { useLocalSearchParams } from 'expo-router';
export default function PlayerScreen() {
const { id } = useLocalSearchParams<{ id: string }>();
return <PlayerProfile playerId={id} />;
}
```
### Catch-All Routes `[...slug]`
```typescript
// app/[...missing].tsx
import { Link, Stack } from 'expo-router';
export default function NotFound() {
return (
<>
<Stack.Screen options={{ title: 'Not Found' }} />
<Link href="/">Go home</Link>
</>
);
}
```
## Navigation Patterns
### Programmatic Navigation
```typescript
import { useRouter, Link } from 'expo-router';
function MyComponent() {
const router = useRouter();
// Navigate with push (adds to stack)
router.push('/player/123');
// Navigate with params
router.push({
pathname: '/player/[id]',
params: { id: '123' },
});
// Replace current screen (no back)
router.replace('/home');
// Go back
router.back();
// Navigate to root
router.navigate('/');
// Dismiss modal
router.dismiss();
}
```
### Link Component
```tsx
import { Link } from 'expo-router';
// Simple link
<Link href="/settings">Settings</Link>
// With params
<Link href={{ pathname: '/player/[id]', params: { id: '123' } }}>
View Player
</Link>
// As button
<Link href="/schedule" asChild>
<Pressable>
<Text>View Schedule</Text>
</Pressable>
</Link>
// Replace instead of push
<Link href="/home" replace>Home</Link>
```
## Stack Navigation
```typescript
// app/_layout.tsx
import { Stack } from 'expo-router';
export default function RootLayout() {
return (
<Stack>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen
name="modal"
options={{ presentation: 'modal' }}
/>
<Stack.Screen
name="player/[id]"
options={{
headerTitle: 'Player',
headerBackTitle: 'Back',
}}
/>
</Stack>
);
}
```
### Dynamic Header Options
```typescript
// app/player/[id].tsx
import { Stack, useLocalSearchParams } from 'expo-router';
export default function PlayerScreen() {
const { id } = useLocalSearchParams();
const player = usePlayer(id);
return (
<>
<Stack.Screen
options={{
headerTitle: player?.name ?? 'Loading...',
headerRight: () => (
<EditButton playerId={id} />
),
}}
/>
<PlayerProfile player={player} />
</>
);
}
```
## Modals
```typescript
// Present as modal from anywhere
router.push('/booking-modal');
// app/booking-modal.tsx
import { Stack, useRouter } from 'expo-router';
export default function BookingModal() {
const router = useRouter();
const handleComplete = () => {
router.dismiss(); // or router.back()
};
return (
<>
<Stack.Screen
options={{
presentation: 'modal',
headerLeft: () => (
<Button title="Cancel" onPress={() => router.dismiss()} />
),
}}
/>
<BookingForm onComplete={handleComplete} />
</>
);
}
// In _layout.tsx, configure the modal screen
<Stack.Screen
name="booking-modal"
options={{
presentation: 'modal',
headerShown: true,
}}
/>
```
## Deep Linking
### Configure scheme in app.json
```json
{
"expo": {
"scheme": "myapp",
"ios": {
"bundleIdentifier": "com.yourcompany.myapp",
"associatedDomains": ["applinks:yourdomain.com"]
}
}
}
```
### Test deep links
```bash
# iOS Simulator
npx uri-scheme open "myapp://player/123" --ios
# Physical device
npx expo start --dev-client
# Then open myapp://player/123 in Safari
```
### Universal Links (iOS)
1. Add `associatedDomains` to app.json
2. Host `apple-app-site-association` file at `https://yourdomain.com/.well-known/apple-app-site-association`:
```json
{
"applinks": {
"apps": [],
"details": [{
"appID": "TEAMID.com.yourcompany.myapp",
"paths": ["/player/*", "/schedule/*"]
}]
}
}
```
### Handle incoming links
```typescript
// app/_layout.tsx
import { useEffect } from 'react';
import * as Linking from 'expo-linking';
import { useRouter } from 'expo-router';
export default function RootLayout() {
const router = useRouter();
useEffect(() => {
// Handle link that opened the app
Linking.getInitialURL().then((url) => {
if (url) handleDeepLink(url);
});
// Handle links while app is open
const subscription = Linking.addEventListener('url', ({ url }) => {
handleDeepLink(url);
});
return () => subscription.remove();
}, []);
function handleDeepLink(url: string) {
const { path, queryParams } = Linking.parse(url);
// Expo Router handles most cases automatically
// Custom logic here for special cases
}
return <Stack />;
}
```
## Common Patterns
### Auth-Protected Routes
See `rn-auth` skill for full auth context pattern. Key navigation piece:
```typescript
// app/_layout.tsx
export default function RootLayout() {
const { token, isLoading } = useAuth();
const segments = useSegments();
const router = useRouter();
useEffect(() => {
if (isLoading) return;
const inAuthGroup = segments[0] === '(auth)';
if (!token && !inAuthGroup) {
router.replace('/(auth)/login');
} else if (token && inAuthGroup) {
router.replace('/(tabs)/home');
}
}, [token, isLoading]);
return (
<Stack>
<Stack.Screen name="(auth)" options={{ headerShown: false }} />
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
</Stack>
);
}
```
### Preventing Back Navigation
```typescript
// After login success, replace to prevent back to login
router.replace('/(tabs)/home');
// For onboarding completion
router.replace('/home');
// In screen options
<Stack.Screen
name="checkout-complete"
options={{
headerBackVisible: false,
gestureEnabled: false, // Prevent swipe back
}}
/>
```
### Passing Data Between Screens
```typescript
// Option 1: URL params (simple data, survives refresh)
router.push({
pathname: '/confirm',
params: { date: '2025-01-15', courtId: '5' },
});
// Reading
const { date, courtId } = useLocalSearchParams();
// Option 2: Global state for complex data (doesn't survive refresh)
// Use context, zustand, or similar
```
## Debugging Navigation
### Log current route
```typescript
import { usePathname, useSegments } from 'expo-router';
function DebugNav() {
const pathname = usePathname();
const segments = useSegments();
console.log('Current path:', pathname);
console.log('Segments:', segments);
return null;
}
```
### Common issues
| Issue | Solution |
|-------|----------|
| Screen not found | Check file name matches route, check `_layout.tsx` includes screen |
| Tabs not showing | Ensure tab screens are direct children of tab `_layout.tsx` |
| Back button missing | Check `headerShown` in parent and child layouts |
| Deep link not working | Verify scheme in app.json, test with `uri-scheme` CLI |
| Params undefined | Use `useLocalSearchParams` not `useSearchParams` |Related Skills
keyboard-navigation-tester
Keyboard Navigation Tester - Auto-activating skill for Frontend Development. Triggers on: keyboard navigation tester, keyboard navigation tester Part of the Frontend Development skill category.
Filesystem Navigation
Guidelines for systematically exploring and understanding directory structures.
docs-navigation
Navigate hierarchical ai-docs indexes to find documentation. Check local docs FIRST for speed and curated context before web searching. Covers Claude Code, BAML, MCP, and other tracked libraries.
react-native-navigation
Navigation and deep linking for React Native using React Navigation. Use when setting up navigation stacks or deep linking in React Native with React Navigation. (triggers: **/App.tsx, **/*Navigator.tsx, **/*Screen.tsx, NavigationContainer, createStackNavigator, createBottomTabNavigator, linking, deep link)
react-native-navigation-v6
React Navigation 6+ standards for stack, tab, and deep linking. Use when implementing React Navigation stacks, tabs, or deep linking in React Native. (triggers: **/*Navigation*.tsx, src/navigation/**, navigation, react-navigation, stack, tab, drawer, deep link)
ios-ui-navigation
Standards for UIKit, Auto Layout, and Apple Human Interface Guidelines. Use when implementing UIKit navigation, Auto Layout constraints, or HIG compliance. (triggers: **/*View.swift, **/*.xib, **/*.storyboard, NSLayoutConstraint, UIStackView, SnapKit, layoutSubviews)
ios-navigation
SwiftUI navigation and deep linking using NavigationStack and Universal Links. Use when implementing NavigationStack or Universal Links deep linking in iOS. (triggers: **/*View.swift, **/*App.swift, NavigationStack, NavigationLink, onOpenURL, universalLink, NSUserActivity)
flutter-navigation
Flutter navigation patterns including go_router, deep linking, and named routes. Use when implementing navigation, deep linking, or named routes in Flutter. (triggers: **/*_route.dart, **/*_router.dart, **/main.dart, Navigator, GoRouter, routes, deep link, go_router, AutoRoute)
flutter-go-router-navigation
Typed routes, route state, and redirection using go_router. Use when implementing go_router typed routes, guards, or redirects in Flutter. (triggers: **/router.dart, **/app_router.dart, GoRouter, GoRoute, StatefulShellRoute, redirection, typed-routes)
flutter-getx-navigation
Context-less navigation, named routes, and middleware using GetX. Use when implementing navigation or route middleware with GetX in Flutter. (triggers: **/app_pages.dart, **/app_routes.dart, GetPage, Get.to, Get.off, Get.offAll, Get.toNamed, GetMiddleware)
flutter-auto-route-navigation
Typed routing, nested routes, and guards using auto_route. Use when implementing typed navigation, nested routes, or route guards with auto_route in Flutter. (triggers: **/router.dart, **/app_router.dart, AutoRoute, AutoRouter, router, guards, navigate, push)
android-navigation
Navigation for Android using Jetpack Compose Navigation and App Links. Use when implementing navigation flows, deep links, or backstack handling in Android. (triggers: **/*Screen.kt, **/*Activity.kt, **/NavGraph.kt, NavController, NavHost, composable, navArgument, deepLinks)