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.

242 stars

Best use case

rn-navigation is best used when you need a repeatable AI agent workflow instead of a one-off prompt. It is especially useful for teams working in multi. Expo Router navigation patterns for React Native. Use when implementing navigation, routing, deep links, tab bars, modals, or handling navigation state in Expo apps.

Expo Router navigation patterns for React Native. Use when implementing navigation, routing, deep links, tab bars, modals, or handling navigation state in Expo apps.

Users should expect a more consistent workflow output, faster repeated execution, and less time spent rewriting prompts from scratch.

Practical example

Example input

Use the "rn-navigation" skill to help with this workflow task. Context: Expo Router navigation patterns for React Native. Use when implementing navigation, routing, deep links, tab bars, modals, or handling navigation state in Expo apps.

Example output

A structured workflow result with clearer steps, more consistent formatting, and an output that is easier to reuse in the next run.

When to use this skill

  • Use this skill when you want a reusable workflow rather than writing the same prompt again and again.

When not to use this skill

  • Do not use this when you only need a one-off answer and do not need a reusable workflow.
  • Do not use it if you cannot install or maintain the related files, repository context, or supporting tools.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/rn-navigation/SKILL.md --create-dirs "https://raw.githubusercontent.com/aiskillstore/marketplace/main/skills/cjharmath/rn-navigation/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/rn-navigation/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How rn-navigation Compares

Feature / Agentrn-navigationStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/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

docs-navigation

242
from aiskillstore/marketplace

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.

web-navigation

242
from aiskillstore/marketplace

Navigation and routing patterns for React web applications. Use when implementing React Router, Next.js routing, deep links, or handling navigation state.

azure-quotas

242
from aiskillstore/marketplace

Check/manage Azure quotas and usage across providers. For deployment planning, capacity validation, region selection. WHEN: "check quotas", "service limits", "current usage", "request quota increase", "quota exceeded", "validate capacity", "regional availability", "provisioning limits", "vCPU limit", "how many vCPUs available in my subscription".

DevOps & Infrastructure

raindrop-io

242
from aiskillstore/marketplace

Manage Raindrop.io bookmarks with AI assistance. Save and organize bookmarks, search your collection, manage reading lists, and organize research materials. Use when working with bookmarks, web research, reading lists, or when user mentions Raindrop.io.

Data & Research

zlibrary-to-notebooklm

242
from aiskillstore/marketplace

自动从 Z-Library 下载书籍并上传到 Google NotebookLM。支持 PDF/EPUB 格式,自动转换,一键创建知识库。

discover-skills

242
from aiskillstore/marketplace

当你发现当前可用的技能都不够合适(或用户明确要求你寻找技能)时使用。本技能会基于任务目标和约束,给出一份精简的候选技能清单,帮助你选出最适配当前任务的技能。

web-performance-seo

242
from aiskillstore/marketplace

Fix PageSpeed Insights/Lighthouse accessibility "!" errors caused by contrast audit failures (CSS filters, OKLCH/OKLAB, low opacity, gradient text, image backgrounds). Use for accessibility-driven SEO/performance debugging and remediation.

project-to-obsidian

242
from aiskillstore/marketplace

将代码项目转换为 Obsidian 知识库。当用户提到 obsidian、项目文档、知识库、分析项目、转换项目 时激活。 【激活后必须执行】: 1. 先完整阅读本 SKILL.md 文件 2. 理解 AI 写入规则(默认到 00_Inbox/AI/、追加式、统一 Schema) 3. 执行 STEP 0: 使用 AskUserQuestion 询问用户确认 4. 用户确认后才开始 STEP 1 项目扫描 5. 严格按 STEP 0 → 1 → 2 → 3 → 4 顺序执行 【禁止行为】: - 禁止不读 SKILL.md 就开始分析项目 - 禁止跳过 STEP 0 用户确认 - 禁止直接在 30_Resources 创建(先到 00_Inbox/AI/) - 禁止自作主张决定输出位置

obsidian-helper

242
from aiskillstore/marketplace

Obsidian 智能笔记助手。当用户提到 obsidian、日记、笔记、知识库、capture、review 时激活。 【激活后必须执行】: 1. 先完整阅读本 SKILL.md 文件 2. 理解 AI 写入三条硬规矩(00_Inbox/AI/、追加式、白名单字段) 3. 按 STEP 0 → STEP 1 → ... 顺序执行 4. 不要跳过任何步骤,不要自作主张 【禁止行为】: - 禁止不读 SKILL.md 就开始工作 - 禁止跳过用户确认步骤 - 禁止在非 00_Inbox/AI/ 位置创建新笔记(除非用户明确指定)

internationalizing-websites

242
from aiskillstore/marketplace

Adds multi-language support to Next.js websites with proper SEO configuration including hreflang tags, localized sitemaps, and language-specific content. Use when adding new languages, setting up i18n, optimizing for international SEO, or when user mentions localization, translation, multi-language, or specific languages like Japanese, Korean, Chinese.

google-official-seo-guide

242
from aiskillstore/marketplace

Official Google SEO guide covering search optimization, best practices, Search Console, crawling, indexing, and improving website search visibility based on official Google documentation

github-release-assistant

242
from aiskillstore/marketplace

Generate bilingual GitHub release documentation (README.md + README.zh.md) from repo metadata and user input, and guide release prep with git add/commit/push. Use when the user asks to write or polish README files, create bilingual docs, prepare a GitHub release, or mentions release assistant/README generation.