关于 building-native-ui
building-native-ui 是面向 Expo 应用开发的 UI 指南 Skill,涵盖原生界面、导航、样式与响应式布局。它提供 Expo Go 使用建议和针对组件、路由及平台能力的参考资料。
主要功能
- 01Expo Router 路由与导航规范
- 02原生控件、表单与媒体参考
- 03动画、图标和渐变指导
- 04响应式布局与安全区域建议
- 05Expo Go 与自定义构建选择指南
适用场景
- 01规划 Expo 应用的路由结构
- 02为 iOS 界面选择原生控件和交互
- 03检查布局、样式及导航实现
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ midudev/ autoskills --skill building-native-ui安装后在 Claude Code 中输入/building-native-ui确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/animations.mdcontrols.mdform-sheet.mdgradients.mdicons.mdmedia.mdroute-structure.mdsearch.mdstorage.mdtabs.mdtoolbar-and-headers.mdvisual-effects.mdwebgpu-three.mdzoom-transitions.md详细介绍
Expo UI 指南
参考资料
按需查阅以下资源:
references/
animations.md Reanimated: entering, exiting, layout, scroll-driven, gestures
controls.md Native iOS: Switch, Slider, SegmentedControl, DateTimePicker, Picker
form-sheet.md Form sheets in expo-router: configuration, footers and background interaction.
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
route-structure.md Route conventions, dynamic routes, groups, folder organization
search.md Search bar with headers, useSearch hook, filtering patterns
storage.md SQLite, AsyncStorage, SecureStore
tabs.md NativeTabs, migration from JS tabs, iOS 26 features
toolbar-and-headers.md Stack headers and toolbar buttons, menus, search (iOS only)
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
zoom-transitions.md Apple Zoom: fluid zoom transitions with Link.AppleZoom (iOS 18+)
运行应用
重要:始终先尝试使用 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 targets(通过
@bacons/apple-targets实现的小组件、App Clip、扩展) - Expo Go 未包含的第三方原生模块
- 无法通过
app.json表达的自定义原生配置
Expo Go 适用的情况
Expo Go 开箱即支持大量功能:
- 所有
expo-*包(相机、位置、通知等) - Expo Router 导航
- 大多数 UI 库(reanimated、gesture handler 等)
- 推送通知、深层链接等
如果不确定,请先尝试 Expo Go。 创建自定义构建会增加复杂性、拖慢迭代速度,还需要配置 Xcode/Android Studio。
代码风格
- 注意字符串是否未正确终止。确保对嵌套反引号进行转义;务必正确转义引号。
- 始终在文件顶部使用 import 语句。
- 文件名始终使用 kebab-case,例如
comment-card.tsx - 移动或重构导航时,始终删除旧的路由文件
- 文件名中不得使用特殊字符
- 在 tsconfig.json 中配置路径别名,重构时优先使用别名而非相对导入。
路由
详细的路由约定请参阅 ./references/route-structure.md。
- 路由应放在
app目录中。 - 不要在 app 目录中与路由文件并置组件、类型或工具。这属于反模式。
- 确保应用始终有一个与 "/" 匹配的路由,它可以位于分组路由中。
库的选择
- 不要使用 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实现液态玻璃背景
响应式设计
- 根组件始终应包裹在滚动视图中,以实现响应式布局
- 使用
<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 时,其第一个子元素通常应为设置了
contentInsetAdjustmentBehavior="automatic"的 ScrollView - 向页面添加
ScrollView时,它通常应是路由组件中的第一个组件 - 优先使用 Stack.Screen 选项中的
headerSearchBarOptions添加搜索栏 - 对包含可能需要复制的数据的文本,使用
<Text selectable />属性 - 考虑将大数字格式化为 1.4M 或 38k 等形式
- 除非在 webview 或 Expo DOM 组件中,否则不要使用 'img' 或 'div' 等内置元素
样式
遵循 Apple 人机界面指南。
通用样式规则
- 优先使用 flex gap,而不是 margin 和 padding 样式
- 尽可能优先使用 padding,而不是 margin
- 始终考虑安全区域,可通过堆栈标题、标签页或 ScrollView/FlatList 的
contentInsetAdjustmentBehavior="automatic"实现 - 确保同时考虑顶部和底部安全区域插入值
- 除非复用样式更快捷,否则使用内联样式,而不是 StyleSheet.create
- 为状态变化添加进入和退出动画
- 创建圆角时使用
{ borderCurve: 'continuous' },胶囊形状除外 - 始终使用导航堆栈标题,而不是在页面上自定义文本元素
- 为 ScrollView 添加内边距时,使用
contentContainerStyle设置 padding 和 gap,而不是在 ScrollView 本身设置 padding(可减少裁切) - 不支持 CSS 和 Tailwind,请使用内联样式
文本样式
- 为每个显示重要数据或错误消息的
<Text/>元素添加selectable属性 - 计数器应使用
{ fontVariant: 'tabular-nums' },以保持对齐
阴影
使用 CSS boxShadow 样式属性。绝不要使用旧版 React Native 阴影或 elevation 样式。
<View style={{ boxShadow: "0 1px 2px rgba(0, 0, 0, 0.05)" }} />
支持 inset 阴影。
导航
链接
使用来自 'expo-router' 的 <Link href="/path" /> 在路由之间导航。
import { Link } from 'expo-router';
// Basic link
<Link href="/path" />
// Wrapping custom components
<Link href="/path" asChild>
<Pressable>...</Pressable>
</Link>
尽可能添加 <Link.Preview>,遵循 iOS 规范。经常添加上下文菜单和预览,以增强导航体验。
堆栈
- 始终使用
_layout.tsx文件定义堆栈 - 使用来自 'expo-router/stack' 的 Stack 创建原生导航堆栈
页面标题
在 Stack.Screen 选项中设置页面标题:
<Stack.Screen options={{ title: "Home" }} />
上下文菜单
为 Link 组件添加长按上下文菜单:
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 href="/settings">
<Link.Trigger>
<Pressable>
<Card />
</Pressable>
</Link.Trigger>
<Link.Preview />
</Link>
链接预览可与上下文菜单配合使用。
模态页面
以模态页面的形式呈现屏幕:
<Stack.Screen name="modal" options={{ presentation: "modal" }} />
建议使用此方式,而不是构建自定义模态组件。
表单页
以动态表单页的形式呈现屏幕:
<Stack.Screen
name="sheet"
options={{
presentation: "formSheet",
sheetGrabberVisible: true,
sheetAllowedDetents: [0.5, 1.0],
contentStyle: { backgroundColor: "transparent" },
}}
/>
- 使用
contentStyle: { backgroundColor: "transparent" }可使背景在 iOS 26+ 上呈现液态玻璃效果。
常见路由结构
在每个标签页中包含堆栈的标准应用布局:
app/
_layout.tsx — <NativeTabs />
(index,search)/
_layout.tsx — <Stack />
index.tsx — Main list
search.tsx — Search view
// app/_layout.tsx
import { NativeTabs, Icon, Label } from "expo-router/unstable-native-tabs";
import { Theme } from "../components/theme";
export default function Layout() {
return (
<Theme>
<NativeTabs>
<NativeTabs.Trigger name="(index)">
<Icon sf="list.dash" />
<Label>Items</Label>
</NativeTabs.Trigger>
<NativeTabs.Trigger name="(search)" role="search" />
</NativeTabs>
</Theme>
);
}
创建共享分组路由,以便两个标签页都能推送共同的屏幕:
// app/(index,search)/_layout.tsx
import { Stack } from "expo-router/stack";
import { PlatformColor } from "react-native";
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: PlatformColor("label") },
headerLargeTitle: true,
headerBlurEffect: "none",
headerBackButtonDisplayMode: "minimal",
}}
>
<Stack.Screen name={screen} options={{ title: titles[screen] }} />
<Stack.Screen name="i/[id]" options={{ headerLargeTitle: false }} />
</Stack>
);
}