Expo Router Navigation skill

Navigation and routing for Expo Router.

by expo·MIT license·★ 2,657 Stars on the repo·GitHub ↗

Use now

Files of Expo Router Navigation

expo/main1 file shown
SKILL.md
Show the full text241 lines

Expo Router Navigation

Navigation and routing for Expo Router apps. For screen styling, colors, controls, media, and visual effects, use the expo-native-ui skill; for motion and gestures, use expo-animation.

References

Consult these resources as needed:

references/
  route-structure.md     Route conventions, dynamic routes, groups, folder organization
  tabs.md                NativeTabs, migration from JS tabs, iOS 26 features
  toolbar-and-headers.md Stack headers and toolbar buttons, menus, search (iOS only)
  form-sheet.md          Form sheets in expo-router: configuration, footers and background interaction.
  search.md              Search bar with headers, useSearch hook, filtering patterns
  zoom-transitions.md    Apple Zoom: fluid zoom transitions with Link.AppleZoom (iOS 18+)

Code Style

  • Always use kebab-case for file names, e.g. comment-card.tsx
  • Always remove old route files when moving or restructuring navigation
  • Never use special characters in file names
  • Configure tsconfig.json with path aliases, and prefer aliases over relative imports for refactors.

Routes

See ./references/route-structure.md for detailed route conventions.

  • Routes belong in the app directory.
  • Never co-locate components, types, or utilities in the app directory. This is an anti-pattern.
  • Ensure the app always has a route that matches "/", it may be inside a group route.

Library Preferences

  • Color from expo-router for native semantic colors, not raw PlatformColor (type-safe, auto-adapts to light/dark). See expo-native-ui for the full color palette pattern.
  • In SDK 56+, never import from @react-navigation/* directly — use expo-router/react-navigation instead (covers @react-navigation/native, /core, /elements, /routers)

Behavior

  • Prefer Stack.SearchBar to add a search bar to a screen

Navigation

Use <Link href="/path" /> from 'expo-router' for navigation between routes.

import { Link } from 'expo-router';

// Basic link
<Link href="/path" />

// Wrapping custom components
<Link href="/path" asChild>
  <Pressable>...</Pressable>
</Link>

Whenever possible, include a <Link.Preview> to follow iOS conventions. Add context menus and previews frequently to enhance navigation.

Stack

  • ALWAYS use _layout.tsx files to define stacks
  • Use Stack from 'expo-router/stack' for native navigation stacks
Page Title

Set the page title with Stack.Title:

<Stack.Title>Home</Stack.Title>

Context Menus

Add long press context menus to Link components:

import { Link } from "expo-router";

<Link href="/settings" asChild>
  <Link.Trigger>
    <Pressable>
      <Card />
    </Pressable>
  </Link.Trigger>
  <Link.Menu>
    <Link.MenuAction
      title="Share"
      icon="square.and.arrow.up"
      onPress={handleSharePress}
    />
    <Link.MenuAction
      title="Block"
      icon="nosign"
      destructive
      onPress={handleBlockPress}
    />
    <Link.Menu title="More" icon="ellipsis">
      <Link.MenuAction title="Copy" icon="doc.on.doc" onPress={() => {}} />
      <Link.MenuAction
        title="Delete"
        icon="trash"
        destructive
        onPress={() => {}}
      />
    </Link.Menu>
  </Link.Menu>
</Link>;

Use link previews frequently to enhance navigation:

<Link href="/settings">
  <Link.Trigger>
    <Pressable>
      <Card />
    </Pressable>
  </Link.Trigger>
  <Link.Preview />
</Link>

Link preview can be used with context menus.

Modal

Present a screen as a modal:

<Stack.Screen name="modal" options={{ presentation: "modal" }} />

Prefer this to building a custom modal component.

Sheet

Present a screen as a dynamic form sheet:

<Stack.Screen
  name="sheet"
  options={{
    presentation: "formSheet",
    sheetGrabberVisible: true,
    sheetAllowedDetents: [0.5, 1.0],
    contentStyle: { backgroundColor: "transparent" },
  }}
/>
  • Using contentStyle: { backgroundColor: "transparent" } makes the background liquid glass on iOS 26+.

Common route structure

A standard app layout with tabs and stacks inside each tab:

app/
  _layout.tsx — <NativeTabs />
  (index,search)/
    _layout.tsx — <Stack />
    index.tsx — Main list
    search.tsx — Search view
// app/_layout.tsx
import { NativeTabs } from "expo-router/unstable-native-tabs";
import { ThemeProvider, DarkTheme, DefaultTheme } from "expo-router/react-navigation";
import { useColorScheme } from "react-native";

export default function Layout() {
  const colorScheme = useColorScheme();
  return (
    <ThemeProvider value={colorScheme === "dark" ? DarkTheme : DefaultTheme}>
      <NativeTabs>
        <NativeTabs.Trigger name="(index)">
          <NativeTabs.Trigger.Icon sf="list.dash" md="list" />
          <NativeTabs.Trigger.Label>Items</NativeTabs.Trigger.Label>
        </NativeTabs.Trigger>
        <NativeTabs.Trigger name="(search)" role="search" />
      </NativeTabs>
    </ThemeProvider>
  );
}

Create a shared group route so both tabs can push common screens:

// app/(index,search)/_layout.tsx
import { Stack } from "expo-router/stack";
import { colors } from "@/theme/colors";

export default function Layout({ segment }) {
  const screen = segment.match(/\((.*)\)/)?.[1]!;
  const titles: Record<string, string> = { index: "Items", search: "Search" };

  return (
    <Stack
      screenOptions={{
        headerTransparent: true,
        headerShadowVisible: false,
        headerLargeTitleShadowVisible: false,
        headerLargeStyle: { backgroundColor: "transparent" },
        headerTitleStyle: { color: colors.label },
        headerLargeTitleEnabled: true,
        headerBlurEffect: "none",
        headerBackButtonDisplayMode: "minimal",
      }}
    >
      <Stack.Screen name={screen} options={{ title: titles[screen] }} />
      <Stack.Screen name="i/[id]" options={{ headerLargeTitleEnabled: false }} />
    </Stack>
  );
}

headerLargeTitleEnabled is the SDK 56+ option name; older SDKs use headerLargeTitle, which is deprecated upstream.

Submitting Feedback

If you encounter errors, misleading or outdated information in this skill, report it so Expo can improve:

npx --yes submit-expo-feedback@latest --category skills --subject "expo-router" "<actionable feedback>"

Only submit when you have something specific and actionable to report. Include as much relevant context as possible. If an AI agent repeatedly failed or the user had to take over an Expo task, load the expo-skill-feedback skill and follow its eval-candidate flow instead of reusing the command above.

1---
2name: expo-router
3description: Navigation and routing for Expo Router. Covers file-based routes, groups and dynamic routes, folder organization, Link with previews and context menus, native Stack, page titles, modals and form sheets, NativeTabs, headers and toolbars, and header search bars.
4version: 1.0.1
5license: MIT
6---
7 
8# Expo Router Navigation
9 
10Navigation and routing for Expo Router apps. For screen styling, colors, controls, media, and visual effects, use the `expo-native-ui` skill; for motion and gestures, use `expo-animation`.
11 
12## References
13 
14Consult these resources as needed:
15 
16```
17references/
18 route-structure.md Route conventions, dynamic routes, groups, folder organization
19 tabs.md NativeTabs, migration from JS tabs, iOS 26 features
20 toolbar-and-headers.md Stack headers and toolbar buttons, menus, search (iOS only)
21 form-sheet.md Form sheets in expo-router: configuration, footers and background interaction.
22 search.md Search bar with headers, useSearch hook, filtering patterns
23 zoom-transitions.md Apple Zoom: fluid zoom transitions with Link.AppleZoom (iOS 18+)
24```
25 
26## Code Style
27 
28- Always use kebab-case for file names, e.g. `comment-card.tsx`
29- Always remove old route files when moving or restructuring navigation
30- Never use special characters in file names
31- Configure tsconfig.json with path aliases, and prefer aliases over relative imports for refactors.
32 
33## Routes
34 
35See `./references/route-structure.md` for detailed route conventions.
36 
37- Routes belong in the `app` directory.
38- Never co-locate components, types, or utilities in the app directory. This is an anti-pattern.
39- Ensure the app always has a route that matches "/", it may be inside a group route.
40 
41## Library Preferences
42 
43- `Color` from `expo-router` for native semantic colors, not raw `PlatformColor` (type-safe, auto-adapts to light/dark). See `expo-native-ui` for the full color palette pattern.
44- In SDK 56+, never import from `@react-navigation/*` directly — use `expo-router/react-navigation` instead (covers `@react-navigation/native`, `/core`, `/elements`, `/routers`)
45 
46## Behavior
47 
48- Prefer `Stack.SearchBar` to add a search bar to a screen
49 
50# Navigation
51 
52## Link
53 
54Use `<Link href="/path" />` from 'expo-router' for navigation between routes.
55 
56```tsx
57import { Link } from 'expo-router';
58 
59// Basic link
60<Link href="/path" />
61 
62// Wrapping custom components
63<Link href="/path" asChild>
64 <Pressable>...</Pressable>
65</Link>
66```
67 
68Whenever possible, include a `<Link.Preview>` to follow iOS conventions. Add context menus and previews frequently to enhance navigation.
69 
70## Stack
71 
72- ALWAYS use `_layout.tsx` files to define stacks
73- Use Stack from 'expo-router/stack' for native navigation stacks
74 
75### Page Title
76 
77Set the page title with `Stack.Title`:
78 
79```tsx
80<Stack.Title>Home</Stack.Title>
81```
82 
83## Context Menus
84 
85Add long press context menus to Link components:
86 
87```tsx
88import { Link } from "expo-router";
89 
90<Link href="/settings" asChild>
91 <Link.Trigger>
92 <Pressable>
93 <Card />
94 </Pressable>
95 </Link.Trigger>
96 <Link.Menu>
97 <Link.MenuAction
98 title="Share"
99 icon="square.and.arrow.up"
100 onPress={handleSharePress}
101 />
102 <Link.MenuAction
103 title="Block"
104 icon="nosign"
105 destructive
106 onPress={handleBlockPress}
107 />
108 <Link.Menu title="More" icon="ellipsis">
109 <Link.MenuAction title="Copy" icon="doc.on.doc" onPress={() => {}} />
110 <Link.MenuAction
111 title="Delete"
112 icon="trash"
113 destructive
114 onPress={() => {}}
115 />
116 </Link.Menu>
117 </Link.Menu>
118</Link>;
119```
120 
121## Link Previews
122 
123Use link previews frequently to enhance navigation:
124 
125```tsx
126<Link href="/settings">
127 <Link.Trigger>
128 <Pressable>
129 <Card />
130 </Pressable>
131 </Link.Trigger>
132 <Link.Preview />
133</Link>
134```
135 
136Link preview can be used with context menus.
137 
138## Modal
139 
140Present a screen as a modal:
141 
142```tsx
143<Stack.Screen name="modal" options={{ presentation: "modal" }} />
144```
145 
146Prefer this to building a custom modal component.
147 
148## Sheet
149 
150Present a screen as a dynamic form sheet:
151 
152```tsx
153<Stack.Screen
154 name="sheet"
155 options={{
156 presentation: "formSheet",
157 sheetGrabberVisible: true,
158 sheetAllowedDetents: [0.5, 1.0],
159 contentStyle: { backgroundColor: "transparent" },
160 }}
161/>
162```
163 
164- Using `contentStyle: { backgroundColor: "transparent" }` makes the background liquid glass on iOS 26+.
165 
166## Common route structure
167 
168A standard app layout with tabs and stacks inside each tab:
169 
170```
171app/
172 _layout.tsx — <NativeTabs />
173 (index,search)/
174 _layout.tsx — <Stack />
175 index.tsx — Main list
176 search.tsx — Search view
177```
178 
179```tsx
180// app/_layout.tsx
181import { NativeTabs } from "expo-router/unstable-native-tabs";
182import { ThemeProvider, DarkTheme, DefaultTheme } from "expo-router/react-navigation";
183import { useColorScheme } from "react-native";
184 
185export default function Layout() {
186 const colorScheme = useColorScheme();
187 return (
188 <ThemeProvider value={colorScheme === "dark" ? DarkTheme : DefaultTheme}>
189 <NativeTabs>
190 <NativeTabs.Trigger name="(index)">
191 <NativeTabs.Trigger.Icon sf="list.dash" md="list" />
192 <NativeTabs.Trigger.Label>Items</NativeTabs.Trigger.Label>
193 </NativeTabs.Trigger>
194 <NativeTabs.Trigger name="(search)" role="search" />
195 </NativeTabs>
196 </ThemeProvider>
197 );
198}
199```
200 
201Create a shared group route so both tabs can push common screens:
202 
203```tsx
204// app/(index,search)/_layout.tsx
205import { Stack } from "expo-router/stack";
206import { colors } from "@/theme/colors";
207 
208export default function Layout({ segment }) {
209 const screen = segment.match(/\((.*)\)/)?.[1]!;
210 const titles: Record<string, string> = { index: "Items", search: "Search" };
211 
212 return (
213 <Stack
214 screenOptions={{
215 headerTransparent: true,
216 headerShadowVisible: false,
217 headerLargeTitleShadowVisible: false,
218 headerLargeStyle: { backgroundColor: "transparent" },
219 headerTitleStyle: { color: colors.label },
220 headerLargeTitleEnabled: true,
221 headerBlurEffect: "none",
222 headerBackButtonDisplayMode: "minimal",
223 }}
224 >
225 <Stack.Screen name={screen} options={{ title: titles[screen] }} />
226 <Stack.Screen name="i/[id]" options={{ headerLargeTitleEnabled: false }} />
227 </Stack>
228 );
229}
230```
231 
232`headerLargeTitleEnabled` is the SDK 56+ option name; older SDKs use `headerLargeTitle`, which is deprecated upstream.
233 
234## Submitting Feedback
235If you encounter errors, misleading or outdated information in this skill, report it so Expo can improve:
236```bash
237npx --yes submit-expo-feedback@latest --category skills --subject "expo-router" "<actionable feedback>"
238```
239Only submit when you have something specific and actionable to report. Include as much relevant context as possible.
240If an AI agent repeatedly failed or the user had to take over an Expo task, load the expo-skill-feedback skill and follow its eval-candidate flow instead of reusing the command above.
241 

Discussion

Alternatives

`expo-overview` — router & shared rules for Expo / EASEntry point and router for every Expo or EAS task. Load this skill first — before writing code and before choosing another expo-* / eas-* skill — when the request, PRD, or spec mentions Expo, EAS, Expo Go, or an expo-* package, or the project has an `expo` dependency in `package.json`. Within that gate it also covers app specs and designs to implement (tabs, stacks, maps, lists, navigation, building from a screenshot), and phrasings like 'implement a mobile app', 'make my app look native', 'add navigation', 'fetch some data', 'upgrade my SDK', 'add Expo to my existing native app', 'ship to the App Store', or 'I'm new to Expo, where do I start'. A fully specified request (SDK pinned, libraries named, layout given) still routes through here — the shared setup rules still apply. Do NOT load it when neither signal is present: a bare React Native project with no `expo` dependency is not Expo work. Detects the real goal, routes to the right expo-* / eas-* skill, and owns the shared setup rules.Coding · MITAdd an App Clip to an Expo AppAdd an iOS App Clip target to an Expo app. Use when the user mentions App Clip, AASA, apple-app-site-association, appclips, smart app banner, or wants to ship a lightweight iOS Clip invoked from a URL alongside their parent app.Coding · MITApp Store DeploymentBuild and submit iOS and Android apps with EAS to TestFlight, the App Store, or Google Play. Supports Expo and other React Native projects, plus existing native apps. Use for eas.json setup, release pipelines, signing, app versions and build numbers, store submissions, and listing metadata. For Expo websites and API routes, use eas-hosting; for adding React Native screens to a native app, use expo-brownfield.Coding · MITDesigning with SleekUse when the user wants to design a mobile app or UI screens, when they mention their Sleek (sleek.design) projects, or when implementing Sleek designs in code (HTML, React Native, SwiftUI).Coding · MIT