Generated banner for Expo Router
Generated logo for Expo Router

Expo Router

ExpoOfficialPublished by Expo

  • Skill
  • Tool integration
  • Software development
  • MIT
  • Low risk

From the author

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.
— the description by Expo

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

Expo Router provides a file-based routing system for Expo applications, enabling native navigation stacks, tabs, modals, and form sheets. It leverages a directory-based structure where the file system defines the app's route hierarchy. This skill covers route organization, dynamic routing, layout management, and native UI integration for headers, search bars, and context menus to ensure a consistent iOS and Android experience.

What it does

  • Defines app navigation via file-based routing in the app directory.
  • Configures native stacks, tabs, and modal presentations using _layout.tsx files.
  • Implements dynamic routes and catch-all segments for flexible URL handling.
  • Integrates native UI elements like search bars, headers, and context menus.
  • Supports advanced presentation styles including form sheets with custom detents.
  • Provides type-safe navigation and link previews for enhanced user interaction.

When to use it

  • When building a new Expo application requiring native navigation.
  • When migrating from standard React Navigation to a file-based routing architecture.
  • When implementing complex navigation patterns like nested stacks, tabs, or modals.
  • When you need to leverage native iOS/Android navigation features like form sheets or search bars.

Advantages

  • Simplifies navigation logic by mapping the file system directly to app routes.
  • Ensures native performance and look-and-feel across platforms.
  • Promotes clean project structure by separating route definitions from business logic.
  • Provides built-in support for native UI components like context menus and sheet detents.

Usage examples

“How do I create a modal screen in my Expo Router app?”
You define the modal in your _layout.tsx using Stack.Screen and set the presentation option to 'modal'.
“How can I implement a form sheet that slides up from the bottom?”
Configure a Stack.Screen with presentation: 'formSheet', define sheetAllowedDetents, and set the contentStyle background to transparent.
“How do I add a context menu to a link in my app?”
Wrap your component in a Link.Trigger and add a Link.Menu component containing Link.MenuAction items inside the Link.
“What is the best way to organize routes for a tabbed application?”
Use a root _layout.tsx with NativeTabs, then nest individual Stack navigators within group routes for each tab.

Install

  1. Download the skill as a ZIP:

    https://mp-staging.autogineer.com/agent-skills/expo/expo-router/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip expo-router.zip -d .agents/skills

Documentation by Expo

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 @expo/agent-cli@latest feedback --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 agent repeatedly fails at an Expo task or the user has to step in, submit a brief report of the task, expected result, and what went wrong using --category evals and the relevant Expo package or command as --subject.

Files

references/route-structure.md

Route Structure

File Conventions

  • Routes belong in the app directory

  • Use [] for dynamic routes, e.g. [id].tsx

  • Routes can never be named (foo).tsx - use (foo)/index.tsx instead

  • Use (group) routes to simplify the public URL structure

  • NEVER co-locate components, types, or utilities in the app directory - these should be in separate directories like components/, utils/, etc.

  • The app directory should only contain route and _layout files; every file should export a default component

  • Ensure the app always has a route that matches "/" so the app is never blank

  • ALWAYS use _layout.tsx files to define stacks

Dynamic Routes

Use square brackets for dynamic segments:

app/
  users/
    [id].tsx        # Matches /users/123, /users/abc
    [id]/
      posts.tsx     # Matches /users/123/posts
Catch-All Routes

Use [...slug] for catch-all routes:

app/
  docs/
    [...slug].tsx   # Matches /docs/a, /docs/a/b, /docs/a/b/c

Query Parameters

Access query parameters with the useLocalSearchParams hook:

import { useLocalSearchParams } from "expo-router";

function Page() {
  const { id } = useLocalSearchParams<{ id: string }>();
}

For dynamic routes, the parameter name matches the file name:

  • [id].tsx → useLocalSearchParams<{ id: string }>()

  • [slug].tsx → useLocalSearchParams<{ slug: string }>()

Pathname

Access the current pathname with the usePathname hook:

import { usePathname } from "expo-router";

function Component() {
  const pathname = usePathname(); // e.g. "/users/123"
}

Group Routes

Use parentheses for groups that don't affect the URL:

app/
  (auth)/
    login.tsx       # URL: /login
    register.tsx    # URL: /register
  (main)/
    index.tsx       # URL: /
    settings.tsx    # URL: /settings

Groups are useful for:

  • Organizing related routes

  • Applying different layouts to route groups

  • Keeping URLs clean

Stacks and Tabs Structure

When an app has tabs, the header and title should be set in a Stack that is nested INSIDE each tab. This allows tabs to have their own headers and distinct histories. The root layout should often not have a header.

  • Set the 'headerShown' option to false on the tab layout

  • Use (group) routes to simplify the public URL structure

  • You may need to delete or refactor existing routes to fit this structure

Example structure:

app/
  _layout.tsx — <Tabs />
  (home)/
    _layout.tsx — <Stack />
    index.tsx — <ScrollView />
  (settings)/
    _layout.tsx — <Stack />
    index.tsx — <ScrollView />
  (home,settings)/
    info.tsx — <ScrollView /> (shared across tabs)

Array Routes for Multiple Stacks

Use array routes '(index,settings)' to create multiple stacks. This is useful for tabs that need to share screens across stacks.

app/
  _layout.tsx — <Tabs />
  (index,settings)/
    _layout.tsx — <Stack />
    index.tsx — <ScrollView />
    settings.tsx — <ScrollView />

This requires a specialized layout with explicit anchor routes:

// app/(index,settings)/_layout.tsx
import { useMemo } from "react";
import Stack from "expo-router/stack";

export const unstable_settings = {
  index: { anchor: "index" },
  settings: { anchor: "settings" },
};

export default function Layout({ segment }: { segment: string }) {
  const screen = segment.match(/\((.*)\)/)?.[1]!;

  const options = useMemo(() => {
    switch (screen) {
      case "index":
        return { headerRight: () => <></> };
      default:
        return {};
    }
  }, [screen]);

  return (
    <Stack>
      <Stack.Screen name={screen} options={options} />
    </Stack>
  );
}

Complete App Structure Example

app/
  _layout.tsx — <NativeTabs />
  (index,search)/
    _layout.tsx — <Stack />
    index.tsx — Main list
    search.tsx — Search view
    i/[id].tsx — Detail page
components/
  theme.tsx
  list.tsx
utils/
  storage.ts
  use-search.ts

Layout Files

Every directory can have a _layout.tsx file that wraps all routes in that directory:

// app/_layout.tsx
import { Stack } from "expo-router/stack";

export default function RootLayout() {
  return <Stack />;
}
// app/(tabs)/_layout.tsx
import { NativeTabs, Icon, Label } from "expo-router/unstable-native-tabs";

export default function TabLayout() {
  return (
    <NativeTabs>
      <NativeTabs.Trigger name="index">
        <Label>Home</Label>
        <Icon sf="house.fill" />
      </NativeTabs.Trigger>
    </NativeTabs>
  );
}

Route Settings

Export unstable_settings to configure route behavior:

export const unstable_settings = {
  anchor: "index",
};
  • initialRouteName was renamed to anchor in v4

Not Found Routes

Create a +not-found.tsx file to handle unmatched routes:

// app/+not-found.tsx
import { Link } from "expo-router";
import { View, Text } from "react-native";

export default function NotFound() {
  return (
    <View>
      <Text>Page not found</Text>
      <Link href="/">Go home</Link>
    </View>
  );
}

Requirements and compatibility

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Needs: Needs Node.js

Security

What our scanner noticed· Low risk

  • Instructions that use the network

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Published by
Expo
Publisher
Expo (Official)
Source
expo/skills · plugins/expo/skills/expo-router · commit a9e5bd3
Licence
MIT License (redistribution allowed)
  • LICENSE (added by AutoGineer from plugins/expo/LICENSE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by Expo.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download expo-router.zip