expo-native-ui

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Expo Native UI Guidelines

Expo 原生UI指南

For routes, links, stacks, tabs, modals, sheets, and headers, use the
expo-router
skill.
路由、链接、栈、标签页、模态框、底部弹窗和标题栏相关功能,请使用
expo-router
技能。

References

参考资料

Consult these resources as needed:
references/
  animations.md          Reanimated: entering, exiting, layout, scroll-driven, gestures
  controls.md            Native iOS: Switch, Slider, SegmentedControl, DateTimePicker, Picker
  gradients.md           CSS gradients via experimental_backgroundImage (New Arch only)
  icons.md               SF Symbols via expo-image (sf: source), names, animations, weights
  media.md               Camera, audio, video, and file saving
  storage.md             SQLite, AsyncStorage, SecureStore
  visual-effects.md      Blur (expo-blur) and liquid glass (expo-glass-effect)
  webgpu-three.md        3D graphics, games, GPU visualizations with WebGPU and Three.js
按需查阅以下资源:
references/
  animations.md          Reanimated:入场、退场、布局、滚动驱动、手势
  controls.md            原生iOS:Switch、Slider、SegmentedControl、DateTimePicker、Picker
  gradients.md           通过experimental_backgroundImage实现CSS渐变(仅支持新架构)
  icons.md               通过expo-image使用SF Symbols(source为"sf:name")、图标名称、动画、字重
  media.md               相机、音频、视频和文件保存
  storage.md             SQLite、AsyncStorage、SecureStore
  visual-effects.md      模糊效果(expo-blur)和液态玻璃效果(expo-glass-effect)
  webgpu-three.md        使用WebGPU和Three.js实现3D图形、游戏、GPU可视化

Running the App

运行应用

CRITICAL: Always try Expo Go first before creating custom builds.
Most Expo apps work in Expo Go without any custom native code. Before running
npx expo run:ios
or
npx expo run:android
:
  1. Start with Expo Go: Run
    npx expo start
    and scan the QR code with Expo Go
  2. Check if features work: Test your app thoroughly in Expo Go
  3. Only create custom builds when required - see below
重要提示:创建自定义构建前,请始终先尝试使用Expo Go。
大多数Expo应用无需任何自定义原生代码即可在Expo Go中运行。运行
npx expo run:ios
npx expo run:android
之前:
  1. 从Expo Go开始:运行
    npx expo start
    ,使用Expo Go扫描二维码
  2. 验证功能可用性:在Expo Go中全面测试你的应用
  3. 仅在必要时创建自定义构建 - 详见下文

When Custom Builds Are Required

需要自定义构建的场景

You need
npx expo run:ios/android
or
eas build
ONLY when using:
  • Local Expo modules (custom native code in
    modules/
    )
  • Apple targets (widgets, app clips, extensions via
    @bacons/apple-targets
    )
  • Third-party native modules not included in Expo Go
  • Custom native configuration that can't be expressed in
    app.json
仅在使用以下内容时,才需要执行
npx expo run:ios/android
eas build
  • 本地Expo模块
    modules/
    目录下的自定义原生代码)
  • Apple专属目标(小组件、App Clip、通过
    @bacons/apple-targets
    实现的扩展)
  • Expo Go未包含的第三方原生模块
  • 无法在
    app.json
    中配置的自定义原生设置

When Expo Go Works

Expo Go支持的场景

Expo Go supports a huge range of features out of the box:
  • All
    expo-*
    packages (camera, location, notifications, etc.)
  • Expo Router navigation
  • Most UI libraries (reanimated, gesture handler, etc.)
  • Push notifications, deep links, and more
If you're unsure, try Expo Go first. Creating custom builds adds complexity, slower iteration, and requires Xcode/Android Studio setup.
Expo Go开箱即支持大量功能:
  • 所有
    expo-*
    包(相机、定位、通知等)
  • Expo Router导航
  • 大多数UI库(reanimated、手势处理等)
  • 推送通知、深度链接等
如有疑问,请先尝试Expo Go。 创建自定义构建会增加复杂度、降低迭代速度,并且需要配置Xcode/Android Studio。

Code Style

代码风格

  • Be cautious of unterminated strings. Ensure nested backticks are escaped; never forget to escape quotes correctly.
  • Always use import statements at the top of the file.
  • Always use kebab-case for file names, e.g.
    comment-card.tsx
  • Never use special characters in file names
  • Configure tsconfig.json with path aliases, and prefer aliases over relative imports for refactors.
  • 注意未终止的字符串。确保嵌套反引号已转义;切勿忘记正确转义引号。
  • 始终在文件顶部使用导入语句。
  • 文件名始终使用短横线命名法,例如
    comment-card.tsx
  • 文件名中禁止使用特殊字符
  • 在tsconfig.json中配置路径别名,重构时优先使用别名而非相对导入。

Library Preferences

库偏好

  • Never use modules removed from React Native such as Picker, WebView, SafeAreaView, or AsyncStorage
  • Never use legacy expo-permissions
  • expo-audio
    not
    expo-av
  • expo-video
    not
    expo-av
  • expo-image
    with
    source="sf:name"
    for SF Symbols, not
    expo-symbols
    or
    @expo/vector-icons
  • react-native-safe-area-context
    not react-native SafeAreaView
  • process.env.EXPO_OS
    not
    Platform.OS
  • React.use
    not
    React.useContext
  • expo-image
    Image component instead of intrinsic element
    img
  • expo-glass-effect
    for liquid glass backdrops
  • Color
    from
    expo-router
    for native semantic colors, not raw
    PlatformColor
    (type-safe, auto-adapts to light/dark)
  • In SDK 56+, never import from
    @react-navigation/*
    directly — use
    expo-router/react-navigation
    instead (covers
    @react-navigation/native
    ,
    /core
    ,
    /elements
    ,
    /routers
    )
  • 切勿使用React Native已移除的模块,如Picker、WebView、SafeAreaView或AsyncStorage
  • 切勿使用旧版expo-permissions
  • 使用
    expo-audio
    而非
    expo-av
  • 使用
    expo-video
    而非
    expo-av
  • 使用
    expo-image
    并通过
    source="sf:name"
    加载SF Symbols,而非
    expo-symbols
    @expo/vector-icons
  • 使用
    react-native-safe-area-context
    而非react-native的SafeAreaView
  • 使用
    process.env.EXPO_OS
    而非
    Platform.OS
  • 使用
    React.use
    而非
    React.useContext
  • 使用
    expo-image
    的Image组件而非原生
    img
    元素
  • 使用
    expo-glass-effect
    实现液态玻璃背景
  • 使用
    expo-router
    中的
    Color
    获取原生语义化颜色,而非原生
    PlatformColor
    (类型安全,自动适配明暗模式)
  • 在SDK 56及以上版本中,切勿直接从
    @react-navigation/*
    导入——请使用
    expo-router/react-navigation
    替代(涵盖
    @react-navigation/native
    /core
    /elements
    /routers

Responsiveness

响应式设计

  • Always wrap root component in a scroll view for responsiveness
  • Use
    <ScrollView contentInsetAdjustmentBehavior="automatic" />
    instead of
    <SafeAreaView>
    for smarter safe area insets
  • contentInsetAdjustmentBehavior="automatic"
    should be applied to FlatList and SectionList as well
  • Use flexbox instead of Dimensions API
  • ALWAYS prefer
    useWindowDimensions
    over
    Dimensions.get()
    to measure screen size
  • 始终将根组件包裹在滚动视图中以实现响应式
  • 使用
    <ScrollView contentInsetAdjustmentBehavior="automatic" />
    替代
    <SafeAreaView>
    ,实现更智能的安全区域内边距
  • contentInsetAdjustmentBehavior="automatic"
    也应应用于FlatList和SectionList
  • 使用flexbox而非Dimensions API
  • **始终优先使用
    useWindowDimensions
    而非
    Dimensions.get()
    **来测量屏幕尺寸

Behavior

交互行为

  • Use expo-haptics conditionally on iOS to make more delightful experiences
  • Use views with built-in haptics like
    <Switch />
    from React Native and
    @react-native-community/datetimepicker
  • When a route belongs to a Stack, its first child should almost always be a ScrollView with
    contentInsetAdjustmentBehavior="automatic"
    set
  • When adding a
    ScrollView
    to the page it should almost always be the first component inside the route component
  • Use the
    <Text selectable />
    prop on text containing data that could be copied
  • Consider formatting large numbers like 1.4M or 38k
  • Never use intrinsic elements like 'img' or 'div' unless in a webview or Expo DOM component
  • 在iOS上酌情使用expo-haptics,提升体验愉悦感
  • 使用内置触觉反馈的视图,如React Native的
    <Switch />
    @react-native-community/datetimepicker
  • 当路由属于栈导航时,其第一个子组件几乎应始终是设置了
    contentInsetAdjustmentBehavior="automatic"
    的ScrollView
  • 在页面中添加
    ScrollView
    时,它几乎应始终是路由组件内的第一个组件
  • 对包含可复制数据的文本使用
    <Text selectable />
    属性
  • 考虑将大数字格式化为1.4M或38k这样的形式
  • 除非在webview或Expo DOM组件中,否则切勿使用
    img
    div
    等原生元素

Styling

样式设计

Follow Apple Human Interface Guidelines.
遵循Apple人机界面指南(Apple Human Interface Guidelines)。

General Styling Rules

通用样式规则

  • Prefer flex gap over margin and padding styles
  • Prefer padding over margin where possible
  • Always account for safe area, either with stack headers, tabs, or ScrollView/FlatList
    contentInsetAdjustmentBehavior="automatic"
  • Ensure both top and bottom safe area insets are accounted for
  • Inline styles not StyleSheet.create unless reusing styles is faster
  • Add entering and exiting animations for state changes
  • Use
    { borderCurve: 'continuous' }
    for rounded corners unless creating a capsule shape
  • ALWAYS use a navigation stack title instead of a custom text element on the page
  • When padding a ScrollView, use
    contentContainerStyle
    padding and gap instead of padding on the ScrollView itself (reduces clipping)
  • CSS and Tailwind are not supported - use inline styles
  • 优先使用flex gap而非margin和padding样式
  • 尽可能优先使用padding而非margin
  • 始终考虑安全区域,可通过栈标题栏、标签页或ScrollView/FlatList的
    contentInsetAdjustmentBehavior="automatic"
    实现
  • 确保同时处理顶部和底部的安全区域内边距
  • 除非复用样式能提升性能,否则使用内联样式而非StyleSheet.create
  • 为状态变化添加入场和退场动画
  • 圆角优先使用
    { borderCurve: 'continuous' }
    ,除非要创建胶囊形状
  • 始终使用导航栈标题,而非在页面中使用自定义文本元素
  • 为ScrollView添加内边距时,使用
    contentContainerStyle
    的padding和gap,而非直接在ScrollView上设置padding(减少内容裁剪)
  • 不支持CSS和Tailwind,请使用内联样式

Colors

颜色

Use the
Color
API from
expo-router
for native semantic colors. It is a type-safe wrapper over
PlatformColor
that exposes iOS UIKit colors through
Color.ios.*
and Android Material 3 colors through
Color.android.material.*
(static) or
Color.android.dynamic.*
(adapts to the user's wallpaper on Android 12+). These resolve on-device and automatically adapt to light/dark mode and accessibility settings, so you no longer maintain separate light/dark hex tables or a
colors.web.ts
file.
Color
is platform-specific, so wrap each value in
Platform.select
with a
default
hex fallback for web. Centralize the palette in
theme/colors.ts
and import
colors
everywhere:
tsx
// theme/colors.ts
import { Platform } from "react-native";
import { Color } from "expo-router";

export const colors = {
  label: Platform.select({
    ios: Color.ios.label,
    android: Color.android.dynamic.onSurface,
    default: "#000000",
  })!,
  secondaryLabel: Platform.select({
    ios: Color.ios.secondaryLabel,
    android: Color.android.dynamic.onSurfaceVariant,
    default: "#3c3c43",
  })!,
  separator: Platform.select({
    ios: Color.ios.separator,
    android: Color.android.dynamic.outlineVariant,
    default: "#c6c6c8",
  })!,
  systemBackground: Platform.select({
    ios: Color.ios.systemBackground,
    android: Color.android.dynamic.surface,
    default: "#ffffff",
  })!,
  systemBlue: Platform.select({
    ios: Color.ios.systemBlue,
    android: Color.android.dynamic.primary,
    default: "#007aff",
  })!,
};
tsx
import { colors } from "@/theme/colors";

<View style={{ backgroundColor: colors.systemBackground }}>
  <Text style={{ color: colors.label }}>Title</Text>
</View>;
  • iOS re-resolves these colors automatically when the system theme changes. On Android, call
    useColorScheme()
    inside any component that renders them so it re-renders when the theme flips (required when React Compiler memoizes the component).
  • Don't pass
    Color
    /
    PlatformColor
    values into Reanimated styles — use static colors there (see
    references/animations.md
    ).
  • Platform.select({...})!
    returns
    string | OpaqueColorValue
    . Most React Native style props accept
    ColorValue
    (
    string | OpaqueColorValue
    ) so this works fine. But some third-party props only accept
    string
    (e.g.
    tintColor
    on
    expo-image
    ). Cast when needed:
    colors.label as string
    .
使用
expo-router
中的
Color
API获取原生语义化颜色。它是
PlatformColor
的类型安全封装,通过
Color.ios.*
暴露iOS UIKit颜色,通过
Color.android.material.*
(静态)或
Color.android.dynamic.*
(适配Android 12+用户壁纸)暴露Android Material 3颜色。这些颜色在设备端解析,自动适配明暗模式和无障碍设置,因此你无需维护单独的明暗模式十六进制颜色表或
colors.web.ts
文件。
Color
是平台特定的,因此请使用
Platform.select
包裹每个值,并为Web端提供默认十六进制回退色。将调色板集中在
theme/colors.ts
中,并在各处导入
colors
tsx
// theme/colors.ts
import { Platform } from "react-native";
import { Color } from "expo-router";

export const colors = {
  label: Platform.select({
    ios: Color.ios.label,
    android: Color.android.dynamic.onSurface,
    default: "#000000",
  })!,
  secondaryLabel: Platform.select({
    ios: Color.ios.secondaryLabel,
    android: Color.android.dynamic.onSurfaceVariant,
    default: "#3c3c43",
  })!,
  separator: Platform.select({
    ios: Color.ios.separator,
    android: Color.android.dynamic.outlineVariant,
    default: "#c6c6c8",
  })!,
  systemBackground: Platform.select({
    ios: Color.ios.systemBackground,
    android: Color.android.dynamic.surface,
    default: "#ffffff",
  })!,
  systemBlue: Platform.select({
    ios: Color.ios.systemBlue,
    android: Color.android.dynamic.primary,
    default: "#007aff",
  })!,
};
tsx
import { colors } from "@/theme/colors";

<View style={{ backgroundColor: colors.systemBackground }}>
  <Text style={{ color: colors.label }}>Title</Text>
</View>;
  • iOS会在系统主题变化时自动重新解析这些颜色。在Android上,在任何渲染这些颜色的组件内调用
    useColorScheme()
    ,以便主题切换时重新渲染(当React Compiler对组件进行 memoize 时需要此操作)。
  • 请勿将
    Color
    /
    PlatformColor
    值传入Reanimated样式——请在其中使用静态颜色(详见
    references/animations.md
    )。
  • Platform.select({...})!
    返回
    string | OpaqueColorValue
    。大多数React Native样式属性接受
    ColorValue
    string | OpaqueColorValue
    ),因此可以正常使用。但部分第三方属性仅接受
    string
    (例如
    expo-image
    tintColor
    )。必要时进行类型转换:
    colors.label as string

Text Styling

文本样式

  • Add the
    selectable
    prop to every
    <Text/>
    element displaying important data or error messages
  • Counters should use
    { fontVariant: 'tabular-nums' }
    for alignment
  • 对所有显示重要数据或错误信息的
    <Text/>
    元素添加
    selectable
    属性
  • 计数器应使用
    { fontVariant: 'tabular-nums' }
    确保对齐

Shadows

阴影

Use CSS
boxShadow
style prop. NEVER use legacy React Native shadow or elevation styles.
tsx
<View style={{ boxShadow: "0 1px 2px rgba(0, 0, 0, 0.05)" }} />
'inset' shadows are supported.
使用CSS的
boxShadow
样式属性。切勿使用旧版React Native的shadow或elevation样式。
tsx
<View style={{ boxShadow: "0 1px 2px rgba(0, 0, 0, 0.05)" }} />
支持内阴影('inset' shadows)。