expo-router
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseExpo Router Navigation
Expo Router 导航
Navigation and routing for Expo Router apps. For screen styling, colors, controls, animations, media, and visual effects, use the skill.
building-native-uiExpo Router 应用的导航与路由方案。如需进行屏幕样式设置、颜色调整、控件配置、动画、媒体及视觉效果处理,请使用 技能。
building-native-uiReferences
参考资料
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+)按需查阅以下资源:
references/
route-structure.md 路由约定、动态路由、分组、文件夹组织
tabs.md NativeTabs、从JS标签页迁移、iOS 26特性
toolbar-and-headers.md Stack页眉与工具栏按钮、菜单、搜索(仅iOS)
form-sheet.md expo-router中的表单面板:配置、页脚与背景交互
search.md 页眉搜索栏、useSearch钩子、过滤模式
zoom-transitions.md Apple Zoom:使用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.
- 文件名始终使用kebab-case格式,例如
comment-card.tsx - 移动或重构导航结构时,务必删除旧的路由文件
- 文件名中禁止使用特殊字符
- 在tsconfig.json中配置路径别名,重构时优先使用别名而非相对导入
Routes
路由规则
See for detailed route conventions.
./references/route-structure.md- Routes belong in the directory.
app - 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.
详细的路由约定请参阅 。
./references/route-structure.md- 路由文件需放在 目录下
app - 切勿在app目录下存放组件、类型定义或工具类文件,这是反模式
- 确保应用始终存在匹配 "/" 的路由,该路由可位于分组路由内
Library Preferences
库偏好
- from
Colorfor native semantic colors, not rawexpo-router(type-safe, auto-adapts to light/dark). SeePlatformColorfor the full color palette pattern.building-native-ui - In SDK 56+, never import from directly — use
@react-navigation/*instead (coversexpo-router/react-navigation,@react-navigation/native,/core,/elements)/routers
- 使用 中的
expo-router获取原生语义化颜色,而非直接使用Color(类型安全,可自动适配明暗模式)。完整的调色板模式请参阅PlatformColorbuilding-native-ui - 在SDK 56及以上版本中,切勿直接从 导入模块 —— 请改用
@react-navigation/*(涵盖expo-router/react-navigation、@react-navigation/native、/core、/elements)/routers
Behavior
行为规范
- Prefer to add a search bar to a screen
Stack.SearchBar
- 优先使用 为屏幕添加搜索栏
Stack.SearchBar
Navigation
导航组件
Link
Link
Use from 'expo-router' for navigation between routes.
<Link href="/path" />tsx
import { Link } from 'expo-router';
// Basic link
<Link href="/path" />
// Wrapping custom components
<Link href="/path" asChild>
<Pressable>...</Pressable>
</Link>Whenever possible, include a to follow iOS conventions. Add context menus and previews frequently to enhance navigation.
<Link.Preview>使用 提供的 实现路由间导航。
expo-router<Link href="/path" />tsx
import { Link } from 'expo-router';
// 基础链接
<Link href="/path" />
// 包裹自定义组件
<Link href="/path" asChild>
<Pressable>...</Pressable>
</Link>尽可能添加 以遵循iOS规范。经常添加上下文菜单和预览可提升导航体验。
<Link.Preview>Stack
Stack
- ALWAYS use files to define stacks
_layout.tsx - Use Stack from 'expo-router/stack' for native navigation stacks
- 务必使用 文件定义Stack
_layout.tsx - 使用 中的Stack实现原生导航栈
expo-router/stack
Page Title
页面标题
Set the page title with :
Stack.Titletsx
<Stack.Title>Home</Stack.Title>通过 设置页面标题:
Stack.Titletsx
<Stack.Title>Home</Stack.Title>Context Menus
上下文菜单
Add long press context menus to Link components:
tsx
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组件添加长按上下文菜单:
tsx
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
Link预览
Use link previews frequently to enhance navigation:
tsx
<Link href="/settings">
<Link.Trigger>
<Pressable>
<Card />
</Pressable>
</Link.Trigger>
<Link.Preview />
</Link>Link preview can be used with context menus.
经常使用链接预览提升导航体验:
tsx
<Link href="/settings">
<Link.Trigger>
<Pressable>
<Card />
</Pressable>
</Link.Trigger>
<Link.Preview />
</Link>链接预览可与上下文菜单结合使用。
Modal
模态框
Present a screen as a modal:
tsx
<Stack.Screen name="modal" options={{ presentation: "modal" }} />Prefer this to building a custom modal component.
以模态框形式展示屏幕:
tsx
<Stack.Screen name="modal" options={{ presentation: "modal" }} />优先使用这种方式,而非自定义模态框组件。
Sheet
表单面板
Present a screen as a dynamic form sheet:
tsx
<Stack.Screen
name="sheet"
options={{
presentation: "formSheet",
sheetGrabberVisible: true,
sheetAllowedDetents: [0.5, 1.0],
contentStyle: { backgroundColor: "transparent" },
}}
/>- Using makes the background liquid glass on iOS 26+.
contentStyle: { backgroundColor: "transparent" }
以动态表单面板形式展示屏幕:
tsx
<Stack.Screen
name="sheet"
options={{
presentation: "formSheet",
sheetGrabberVisible: true,
sheetAllowedDetents: [0.5, 1.0],
contentStyle: { backgroundColor: "transparent" },
}}
/>- 设置 可在iOS 26及以上版本中实现背景毛玻璃效果。
contentStyle: { backgroundColor: "transparent" }
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 viewtsx
// 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:
tsx
// 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 },
headerLargeTitle: true,
headerBlurEffect: "none",
headerBackButtonDisplayMode: "minimal",
}}
>
<Stack.Screen name={screen} options={{ title: titles[screen] }} />
<Stack.Screen name="i/[id]" options={{ headerLargeTitle: false }} />
</Stack>
);
}包含标签页且每个标签页内有Stack的标准应用布局:
app/
_layout.tsx — <NativeTabs />
(index,search)/
_layout.tsx — <Stack />
index.tsx — 主列表
search.tsx — 搜索视图tsx
// 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>
);
}创建共享分组路由,使两个标签页都能跳转至通用屏幕:
tsx
// 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 },
headerLargeTitle: true,
headerBlurEffect: "none",
headerBackButtonDisplayMode: "minimal",
}}
>
<Stack.Screen name={screen} options={{ title: titles[screen] }} />
<Stack.Screen name="i/[id]" options={{ headerLargeTitle: false }} />
</Stack>
);
}