关于 expo-native-ui
expo-native-ui 是面向 Expo 应用开发者和编码 Agent 的界面开发技能,涵盖布局、样式、平台规范及原生 UI 组件选择建议。
主要功能
- 01Expo Router 布局指导
- 02平台专属样式规范
- 03原生语义颜色建议
- 04Expo Go 与构建建议
- 05UI 参考资料
适用场景
- 01组织 Expo 应用页面
- 02应用 iOS 与 Android 设计规范
- 03判断使用 Expo Go 还是自定义构建
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ expo/ skills --skill expo-native-ui安装后在 Claude Code 中输入/expo-native-ui确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
agents/openai.yamlreferences/controls.mdgradients.mdicons.mdmedia.mdstorage.mdvisual-effects.mdwebgpu-three.md详细介绍
Expo 原生 UI 指南
对于路由、链接、堆栈、标签页、模态框、底部面板和标题栏,请使用 expo-router 技能。对于任何动效——进入/退出、手势、弹簧动画、键盘驱动的 UI——请使用 expo-animation 技能。
选择任何 UI 组件之前,请先查看
expo-ui。@expo/ui提供原生组件——BottomSheet、Button、Picker、Slider、Menu、Section、Switch、SegmentedControl 等——在 iOS 上以真正的 SwiftUI、在 Android 上以 Jetpack Compose 渲染,SDK 56+ 的 Expo Go 中即可使用,无需自定义构建。请加载expo-ui技能,先找到合适的组件,再考虑使用 React Native 内置组件或社区库。本技能(expo-native-ui)涵盖周边结构:Expo Router 导航、布局、样式和视觉效果。
参考资料
按需查阅以下资源:
references/
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-symbols SymbolView: names, weights, animations; Material icons on Android
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
运行应用
重要:创建自定义构建前,务必先尝试使用 Expo Go。
大多数 Expo 应用无需任何自定义原生代码即可在 Expo Go 中运行。运行 npx expo run:ios 或 npx expo run:android 之前:
- 从 Expo Go 开始:运行
npx expo start,并使用 Expo Go 扫描二维码 - 检查功能是否正常:在 Expo Go 中全面测试应用
- 仅在必要时创建自定义构建——见下文
需要自定义构建的情况
仅在使用以下功能时,才需要运行 npx expo run:ios/android 或 eas build:
- 本地 Expo 模块(
modules/中的自定义原生代码) - Apple 目标(通过
@bacons/apple-targets实现的小组件、App Clip、扩展) - Expo Go 未包含的第三方原生模块
- 无法在
app.json中配置的自定义原生配置
Expo Go 可用的情况
Expo Go 开箱即支持多种功能:
- 大多数
expo-*包(相机、位置、传感器、sqlite 等)——但并非全部:自 SDK 53 起,Android 上的 Expo Go 不支持远程推送通知;某些包需要 Expo Go 未打包的原生能力(例如 WebGPU——参见references/webgpu-three.md) - Expo Router 导航和深层链接
- 大多数 UI 库(reanimated、gesture handler 等)
如果不确定,请先尝试 Expo Go。 创建自定义构建会增加复杂度、减慢迭代速度,并且需要配置 Xcode/Android Studio。
代码风格
- 注意字符串是否正确闭合。确保对嵌套反引号进行转义;切勿忘记正确转义引号。
- 始终在文件顶部使用导入语句。
- 文件名始终使用 kebab-case,例如
comment-card.tsx - 文件名中不得使用特殊字符
- 配置 tsconfig.json 使用路径别名,并在重构时优先使用别名,而非相对导入。
库偏好
- 对于任何表单、选择器、滑块、切换开关、菜单或分组表单区域:优先使用
@expo/ui(参见expo-ui技能),再考虑 React Native 内置组件或社区库——它会渲染原生 SwiftUI/Compose,并且可在 SDK 56+ 的 Expo Go 中使用。对于分组/设置样式的行(数量较少且固定),使用@expo/ui的List+ListItem。对于较大或长度未知的滚动列表(信息流、搜索结果、目录),使用FlatList或FlashList——@expo/ui的List不支持虚拟化。 - 切勿使用已从 React Native 移除的模块,例如 Picker、WebView、SafeAreaView 或 AsyncStorage
- 切勿使用旧版 expo-permissions
- 使用
expo-audio,而不是expo-av - 使用
expo-video,而不是expo-av - 在 iOS 上使用
expo-symbols(SymbolView)显示 SF Symbols,而不是@expo/vector-icons——参见references/icons.md。SF Symbols 仅适用于 Apple 平台:在 Android 上,每个图标都必须有 Material 来源(NativeTabs 触发器使用md属性;屏幕内选项参见 icons.md 中的“Android: Material Icons”),切勿只使用 SF 图标 - 使用
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)
响应式布局
- 带有可滚动内容的屏幕应使用 ScrollView 包裹。根元素为 FlatList/FlashList 的屏幕不得再添加外层 ScrollView(列表本身就是滚动容器),而全屏显示的屏幕(相机、地图、画布)则无需使用
- 使用
<ScrollView contentInsetAdjustmentBehavior="automatic" />,而不是<SafeAreaView>,以更智能地处理安全区域内边距 - FlatList 和 SectionList 也应设置
contentInsetAdjustmentBehavior="automatic" - 使用 Flexbox,而不是 Dimensions API
- 始终优先使用
useWindowDimensions,而不是Dimensions.get(),来测量屏幕尺寸
行为
- 在 iOS 上按需使用 expo-haptics,带来更令人愉悦的体验
- 使用内置触感反馈的视图,例如 React Native 的
<Switch />和@react-native-community/datetimepicker - 当 Stack 路由包含可滚动内容时,将 ScrollView(或 FlatList)作为路由内的第一个组件,并设置
contentInsetAdjustmentBehavior="automatic" - 对包含可复制数据的文本使用
<Text selectable />属性 - 考虑将较大的数字格式化为 1.4M 或 38k 这样的形式
- 除非在 webview 或 Expo DOM 组件中,否则绝不使用
img或div等内置元素 - 每个加载数据的屏幕都有四种状态(加载、错误、空、内容)——首次加载仍在进行时,绝不显示空状态;相关规则见
expo-data-fetching技能 - 对可滚动表单和搜索结果,使用
keyboardShouldPersistTaps="handled",让控件响应首次点击,并允许未处理的点击关闭键盘。只有在未处理的点击也应保持键盘打开时,才使用"always" - 表单的主要操作绝不能被键盘遮挡。对于需要跟踪键盘实际边框的 UI,请加载
expo-animation技能中的键盘方案(react-native-keyboard-controller)——绝不使用Keyboard.addListener配合计时动画 - 每个启用的控件都必须执行其所声明的操作:搜索应筛选结果,Save 应提交编辑内容,设置应影响行为。空处理程序和成功提示不是实现;如果用户要求的是原型,本地状态就足够
- 对于异步保存,按
expo-data-fetching的要求保留草稿并处理待处理和失败状态;保存成功前不要关闭表单
在宣布屏幕完成前,完整走查其主要任务,包括加载或保存时的一次失败及恢复流程。检查键盘操作和返回/关闭行为。测试较长的标题、缺失的图片、无搜索结果以及较大的系统字体;确保所需操作始终可用。报告已测试的内容以及无法运行的内容。
样式
遵循各平台自身的设计语言:iOS 上采用 Apple Human Interface Guidelines,Android 上采用 Material Design 3。绝不要让一个平台套用另一个平台的风格——iOS 布局中不要使用 FAB 或波纹效果;Android 上不要手工构建 iOS 风格的界面元素(返回尖括号、大标题文本、iOS 风格的开关)。
通用样式规则
- 优先使用 flex gap,而非 margin 和 padding 样式
- 尽可能优先使用 padding,而非 margin
- 始终考虑安全区域,可通过 Stack 标题栏、标签栏或 ScrollView/FlatList 的
contentInsetAdjustmentBehavior="automatic"实现 - 确保顶部和底部的安全区域内边距都得到考虑
- 除非复用样式更快,否则使用内联样式,不使用 StyleSheet.create
- 进行任何动效或动画工作时,加载
expo-animation技能——该技能负责决定是否使用动画、确定时长以及处理中断规则 - 除非要创建胶囊形状,否则圆角使用
{ borderCurve: 'continuous' } - 始终使用导航 Stack 标题,而非页面上的自定义文本元素
- 为 ScrollView 添加内边距时,使用
contentContainerStyle设置 padding 和 gap,而不是直接在 ScrollView 上设置 padding(可减少裁切) - 不支持 CSS 和 Tailwind——请使用内联样式
颜色
使用 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 提供 default 十六进制颜色回退值。在 theme/colors.ts 中集中管理调色板,并在所有位置导入 colors:
// 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",
})!,
secondarySystemBackground: Platform.select({
ios: Color.ios.secondarySystemBackground,
android: Color.android.dynamic.surfaceVariant,
default: "#f2f2f7",
})!,
systemBlue: Platform.select({
ios: Color.ios.systemBlue,
android: Color.android.dynamic.primary,
default: "#007aff",
})!,
};
import { colors } from "@/theme/colors";
<View style={{ backgroundColor: colors.systemBackground }}>
<Text style={{ color: colors.label }}>Title</Text>
</View>;
- iOS 会在系统主题变化时自动重新解析这些颜色。在 Android 上,请在渲染这些颜色的任意组件中调用
useColorScheme(),以便主题切换时触发重新渲染(React Compiler 会记忆化组件时,这是必需的)。 - 不要将
Color/PlatformColor值传入 Reanimated 样式——它们是不透明的原生颜色对象,而非字符串;请在此类样式中使用静态颜色。 Platform.select({...})!返回string | OpaqueColorValue。大多数 React Native 样式属性接受ColorValue(string | OpaqueColorValue),因此可以正常使用。但某些第三方属性只接受string(例如expo-image中的tintColor)。必要时进行类型转换:colors.label as string。
文本样式
- 对所有显示重要数据或错误消息的
<Text/>元素添加selectable属性 - 计数器应使用
{ fontVariant: 'tabular-nums' }进行对齐
阴影
使用 CSS boxShadow 样式属性。切勿使用旧版 React Native 阴影或 elevation 样式。
<View style={{ boxShadow: "0 1px 2px rgba(0, 0, 0, 0.05)" }} />
支持 'inset' 阴影。
提交反馈
如果你在此技能中发现错误、误导性信息或过时信息,请进行反馈,以便 Expo 改进:
npx --yes @expo/agent-cli@latest feedback --category skills --subject "expo-native-ui" "<actionable feedback>"
仅在有具体且可操作的问题时提交。请尽可能提供相关背景信息。
如果 Agent 反复无法完成 Expo 任务,或用户不得不介入,请使用 --category evals 和相关 Expo 软件包或命令作为 --subject,简要报告任务、预期结果以及出现的问题。