building-native-ui
针对Expo项目构建原生UI的AI代理技能集,提供智能辅助,帮助移动开发者高效设计和实现跨平台用户界面。
npx skills add expo/skills --skill building-native-uiBefore / After 效果对比
1 组手动编写原生UI代码复杂,耗时且易出错。难以快速实现跨平台一致性,影响移动应用开发效率。
智能辅助原生UI构建,自动生成高质量代码。显著提升开发效率,确保UI一致性,加速移动应用发布。
description SKILL.md
building-native-ui
Expo UI Guidelines
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
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+)
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:
-
Start with Expo Go: Run
npx expo startand scan the QR code with Expo Go -
Check if features work: Test your app thoroughly in Expo Go
-
Only create custom builds when required - see below
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
When Expo Go Works
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.
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 -
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.
Routes
See ./references/route-structure.md for detailed route conventions.
-
Routes belong in the
appdirectory. -
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.
Library Preferences
-
Never use modules removed from React Native such as Picker, WebView, SafeAreaView, or AsyncStorage
-
Never use legacy expo-permissions
-
expo-audionotexpo-av -
expo-videonotexpo-av -
expo-imagewithsource="sf:name"for SF Symbols, notexpo-symbolsor@expo/vector-icons -
react-native-safe-area-contextnot react-native SafeAreaView -
process.env.EXPO_OSnotPlatform.OS -
React.usenotReact.useContext -
expo-imageImage component instead of intrinsic elementimg -
expo-glass-effectfor liquid glass backdrops
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
useWindowDimensionsoverDimensions.get()to measure screen size
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
ScrollViewto the page it should almost always be the first component inside the route component -
Prefer
headerSearchBarOptionsin Stack.Screen options to add a search bar -
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
Styling
Follow 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
contentContainerStylepadding and gap instead of padding on the ScrollView itself (reduces clipping) -
CSS and Tailwind are not supported - use inline styles
Text Styling
-
Add the
selectableprop to every<Text/>element displaying important data or error messages -
Counters should use
{ fontVariant: 'tabular-nums' }for alignment
Shadows
Use CSS boxShadow style prop. NEVER use legacy React Native shadow or elevation styles.
<View style={{ boxShadow: "0 1px 2px rgba(0, 0, 0, 0.05)" }} />
'inset' shadows are supported.
Navigation
Link
Use <Link href="/path" /> from 'expo-router' for navigation between routes.
import { Link } from 'expo-router';
// Basic link
<Link href="/path" />
// Wrapping custom components
<Link href="/path" asChild>
<Pressable>...</Pressable>
</Link>
Whenever possible, include a <Link.Preview> to follow iOS conventions. Add context menus and previews frequently to enhance navigation.
Stack
-
ALWAYS use
_layout.tsxfiles to define stacks -
Use Stack from 'expo-router/stack' for native navigation stacks
Page Title
Set the page title in Stack.Screen options:
<Stack.Screen options={{ title: "Home" }} />
Context Menus
Add long press context menus to Link components:
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
Use link previews frequently to enhance navigation:
<Link href="/settings">
<Link.Trigger>
<Pressable>
<Card />
</Pressable>
</Link.Trigger>
<Link.Preview />
</Link>
Link preview can be used with context menus.
Modal
Present a screen as a modal:
<Stack.Screen name="modal" options={{ presentation: "modal" }} />
Prefer this to building a custom modal component.
Sheet
Present a screen as a dynamic form sheet:
<Stack.Screen
name="sheet"
options={{
presentation: "formSheet",
sheetGrabberVisible: true,
sheetAllowedDetents: [0.5, 1.0],
contentStyle: { backgroundColor: "transparent" },
}}
/>
- Using
contentStyle: { backgroundColor: "transparent" }makes the background liquid glass on iOS 26+.
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 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>
);
}
Create a shared group route so both tabs can push common screens:
// 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>
);
}
Weekly Installs19.0KRepositoryexpo/skillsGitHub Stars1.5KFirst SeenJan 19, 2026Security AuditsGen Agent Trust HubPassSocketPassSnykPassInstalled onopencode14.4Kcodex13.8Kgemini-cli13.7Kgithub-copilot13.4Kcursor12.9Kclaude-code11.3K
forum用户评价 (0)
发表评价
暂无评价,来写第一条吧
统计数据
用户评分
为此 Skill 评分