| name | expo-router |
| description | 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. |
| version | 1.0.0 |
| license | MIT |
Expo Router Navigation
Navigation and routing for Expo Router apps. For screen styling, colors, controls, animations, media, and visual effects, use the building-native-ui skill.
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 building-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
Link
Use <Link href="/path" /> from 'expo-router' for navigation between routes.
import { Link } from 'expo-router';
<Link href="/path" />
<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>;
Link Previews
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
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:
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 },
headerLargeTitle: true,
headerBlurEffect: "none",
headerBackButtonDisplayMode: "minimal",
}}
>
<Stack.Screen name={screen} options={{ title: titles[screen] }} />
<Stack.Screen name="i/[id]" options={{ headerLargeTitle: false }} />
</Stack>
);
}