# Navigation Reference
Expo Router file-based navigation: Stack, Tabs, modals, links, and context menus.
## File Conventions
```
app/
_layout.tsx Root layout (providers, NativeTabs)
index.tsx → /
about.tsx → /about
user/
[id].tsx → /user/:id
[id]/
posts.tsx → /user/:id/posts
(tabs)/
_layout.tsx Tab navigator (group, not in URL)
home.tsx → /home
profile.tsx → /profile
(index,search)/
_layout.tsx Shared Stack for both tabs
index.tsx → /
search.tsx → /search
i/[id].tsx → /i/:id (shared detail screen)
api/
users+api.ts → /api/users (server route)
```
**Rules**:
- Routes live only in `app/` — never co-locate components, types, or utils there
- Always have a route matching `/` (may be inside a group)
- Remove old route files when restructuring navigation
- Use kebab-case filenames
## Root Layout (Stack)
```tsx
// app/_layout.tsx — root is always a Stack
import { Stack } from "expo-router";
export default function RootLayout() {
return (
);
}
```
**Always set page title via `Stack.Screen options.title`**, never use a custom Text element as a title.
## Tabs — Which to Use
| Scenario | Use |
|----------|-----|
| Custom design system, cross-platform | **JS Tabs** (stable, fully customizable) |
| iOS-native look, Liquid Glass (iOS 26+) | **NativeTabs** (alpha, limited customization) |
## JS Tabs
```tsx
// app/(tabs)/_layout.tsx
import { Tabs } from "expo-router";
import { Ionicons } from "@expo/vector-icons";
export default function TabLayout() {
return (
,
}}
/>
);
}
```
## NativeTabs (alpha, iOS 18+)
> Alpha API — all tabs render at once, limited customization, max 5 tabs on Android. Use when you want native iOS look (Liquid Glass, native blur/transitions) without rebuilding it yourself.
```tsx
import { NativeTabs } from "expo-router/unstable-native-tabs";
export default function Layout() {
return (
Home
Profile
);
}
```
## Shared Stack for Multiple Tabs
```tsx
// app/(index,search)/_layout.tsx — shared Stack for both index and search tabs
import { Stack } from "expo-router/stack";
const tabLabels: Record = { index: "Home", search: "Explore" };
export default function Layout({ segment }: { segment: string }) {
const activeTab = segment.replace(/[()]/g, "");
return (
);
}
```
## Link Component
```tsx
import { Link } from "expo-router";
// Basic navigation
About
// Dynamic routes
Profile
// Wrapping custom component
Settings
```
## Programmatic Navigation
```tsx
import { useRouter, useLocalSearchParams } from "expo-router";
const router = useRouter();
router.push("/settings");
router.replace("/login"); // No back button
router.back();
// Access route params
const { id } = useLocalSearchParams<{ id: string }>();
```
## Modals & Sheets
```tsx
// Modal presentation
// Form sheet with detents
```
## Context Menus on Links
```tsx
{}} />
```
## Link Previews (iOS only, requires Expo SDK 54+)
```tsx
{/* Shows peek preview on 3D touch / long press */}
```
## Header Search Bar
```tsx
// In Stack.Screen — preferred over building custom search UI
setQuery(e.nativeEvent.text),
onCancelButtonPress: () => setQuery(""),
},
}}
/>
```
## Deep Linking
```json
// app.json
{
"expo": {
"scheme": "myapp",
"ios": {
"associatedDomains": ["applinks:myapp.example.com"]
},
"android": {
"intentFilters": [
{
"action": "VIEW",
"autoVerify": true,
"data": [{ "scheme": "https", "host": "myapp.example.com" }],
"category": ["BROWSABLE", "DEFAULT"]
}
]
}
}
}
```
Expo Router handles deep links automatically — `/user/123` maps to `app/user/[id].tsx`.
## ScrollView in Routes
When a route belongs to a Stack, its first child should almost always be a ScrollView:
```tsx
export default function HomeScreen() {
return (
{/* Content */}
);
}
```
Use `contentInsetAdjustmentBehavior="automatic"` on `ScrollView`, `FlatList`, and `SectionList` — this handles safe areas and header insets automatically. Prefer it over ``.