# 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 ``.