expo-native-ui
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseExpo Native UI Guidelines
Expo 原生UI指南
For routes, links, stacks, tabs, modals, sheets, and headers, use the skill.
expo-router路由、链接、栈、标签页、模态框、底部弹窗和标题栏相关功能,请使用 技能。
expo-routerReferences
参考资料
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 or :
npx expo run:iosnpx expo run:android- Start with Expo Go: Run and scan the QR code with Expo Go
npx expo start - Check if features work: Test your app thoroughly in Expo Go
- Only create custom builds when required - see below
重要提示:创建自定义构建前,请始终先尝试使用Expo Go。
大多数Expo应用无需任何自定义原生代码即可在Expo Go中运行。运行 或 之前:
npx expo run:iosnpx expo run:android- 从Expo Go开始:运行 ,使用Expo Go扫描二维码
npx expo start - 验证功能可用性:在Expo Go中全面测试你的应用
- 仅在必要时创建自定义构建 - 详见下文
When Custom Builds Are Required
需要自定义构建的场景
You need or ONLY when using:
npx expo run:ios/androideas build- 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/androideas 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 packages (camera, location, notifications, etc.)
expo-* - 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
- not
expo-audioexpo-av - not
expo-videoexpo-av - with
expo-imagefor SF Symbols, notsource="sf:name"orexpo-symbols@expo/vector-icons - not react-native SafeAreaView
react-native-safe-area-context - not
process.env.EXPO_OSPlatform.OS - not
React.useReact.useContext - Image component instead of intrinsic element
expo-imageimg - for liquid glass backdrops
expo-glass-effect - from
Colorfor native semantic colors, not rawexpo-router(type-safe, auto-adapts to light/dark)PlatformColor - In SDK 56+, never import from directly — use
@react-navigation/*instead (coversexpo-router/react-navigation,@react-navigation/native,/core,/elements)/routers
- 切勿使用React Native已移除的模块,如Picker、WebView、SafeAreaView或AsyncStorage
- 切勿使用旧版expo-permissions
- 使用而非
expo-audioexpo-av - 使用而非
expo-videoexpo-av - 使用并通过
expo-image加载SF Symbols,而非source="sf:name"或expo-symbols@expo/vector-icons - 使用而非react-native的SafeAreaView
react-native-safe-area-context - 使用而非
process.env.EXPO_OSPlatform.OS - 使用而非
React.useReact.useContext - 使用的Image组件而非原生
expo-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 instead of
<ScrollView contentInsetAdjustmentBehavior="automatic" />for smarter safe area insets<SafeAreaView> - should be applied to FlatList and SectionList as well
contentInsetAdjustmentBehavior="automatic" - Use flexbox instead of Dimensions API
- ALWAYS prefer over
useWindowDimensionsto measure screen sizeDimensions.get()
- 始终将根组件包裹在滚动视图中以实现响应式
- 使用替代
<ScrollView contentInsetAdjustmentBehavior="automatic" />,实现更智能的安全区域内边距<SafeAreaView> - 也应应用于FlatList和SectionList
contentInsetAdjustmentBehavior="automatic" - 使用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 from React Native and
<Switch />@react-native-community/datetimepicker - When a route belongs to a Stack, its first child should almost always be a ScrollView with set
contentInsetAdjustmentBehavior="automatic" - When adding a to the page it should almost always be the first component inside the route component
ScrollView - Use the prop on text containing data that could be copied
<Text selectable /> - 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 - 当路由属于栈导航时,其第一个子组件几乎应始终是设置了的ScrollView
contentInsetAdjustmentBehavior="automatic" - 在页面中添加时,它几乎应始终是路由组件内的第一个组件
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 for rounded corners unless creating a capsule shape
{ borderCurve: 'continuous' } - ALWAYS use a navigation stack title instead of a custom text element on the page
- When padding a ScrollView, use padding and gap instead of padding on the ScrollView itself (reduces clipping)
contentContainerStyle - CSS and Tailwind are not supported - use inline styles
- 优先使用flex gap而非margin和padding样式
- 尽可能优先使用padding而非margin
- 始终考虑安全区域,可通过栈标题栏、标签页或ScrollView/FlatList的实现
contentInsetAdjustmentBehavior="automatic" - 确保同时处理顶部和底部的安全区域内边距
- 除非复用样式能提升性能,否则使用内联样式而非StyleSheet.create
- 为状态变化添加入场和退场动画
- 圆角优先使用,除非要创建胶囊形状
{ borderCurve: 'continuous' } - 始终使用导航栈标题,而非在页面中使用自定义文本元素
- 为ScrollView添加内边距时,使用的padding和gap,而非直接在ScrollView上设置padding(减少内容裁剪)
contentContainerStyle - 不支持CSS和Tailwind,请使用内联样式
Colors
颜色
Use the API from for native semantic colors. It is a type-safe wrapper over that exposes iOS UIKit colors through and Android Material 3 colors through (static) or (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 file.
Colorexpo-routerPlatformColorColor.ios.*Color.android.material.*Color.android.dynamic.*colors.web.tsColorPlatform.selectdefaulttheme/colors.tscolorstsx
// 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 inside any component that renders them so it re-renders when the theme flips (required when React Compiler memoizes the component).
useColorScheme() - Don't pass /
Colorvalues into Reanimated styles — use static colors there (seePlatformColor).references/animations.md - returns
Platform.select({...})!. Most React Native style props acceptstring | OpaqueColorValue(ColorValue) so this works fine. But some third-party props only acceptstring | OpaqueColorValue(e.g.stringontintColor). Cast when needed:expo-image.colors.label as string
使用中的 API获取原生语义化颜色。它是的类型安全封装,通过暴露iOS UIKit颜色,通过(静态)或(适配Android 12+用户壁纸)暴露Android Material 3颜色。这些颜色在设备端解析,自动适配明暗模式和无障碍设置,因此你无需维护单独的明暗模式十六进制颜色表或文件。
expo-routerColorPlatformColorColor.ios.*Color.android.material.*Color.android.dynamic.*colors.web.tsColorPlatform.selecttheme/colors.tscolorstsx
// 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上,在任何渲染这些颜色的组件内调用,以便主题切换时重新渲染(当React Compiler对组件进行 memoize 时需要此操作)。
useColorScheme() - 请勿将/
Color值传入Reanimated样式——请在其中使用静态颜色(详见PlatformColor)。references/animations.md - 返回
Platform.select({...})!。大多数React Native样式属性接受string | OpaqueColorValue(ColorValue),因此可以正常使用。但部分第三方属性仅接受string | OpaqueColorValue(例如string的expo-image)。必要时进行类型转换:tintColor。colors.label as string
Text Styling
文本样式
- Add the prop to every
selectableelement displaying important data or error messages<Text/> - Counters should use for alignment
{ fontVariant: 'tabular-nums' }
- 对所有显示重要数据或错误信息的元素添加
<Text/>属性selectable - 计数器应使用确保对齐
{ fontVariant: 'tabular-nums' }
Shadows
阴影
Use CSS style prop. NEVER use legacy React Native shadow or elevation styles.
boxShadowtsx
<View style={{ boxShadow: "0 1px 2px rgba(0, 0, 0, 0.05)" }} />'inset' shadows are supported.
使用CSS的样式属性。切勿使用旧版React Native的shadow或elevation样式。
boxShadowtsx
<View style={{ boxShadow: "0 1px 2px rgba(0, 0, 0, 0.05)" }} />支持内阴影('inset' shadows)。