Building Native UI
OpenAIOfficialCreated by Expo TeamDistributed by OpenAI
- Skill
- Guidelines
- Software development
- MIT
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides a structured set of guidelines and best practices for developing native mobile applications with Expo Router. It covers essential topics including project structure, navigation patterns, UI styling, native control integration, and animation techniques. The documentation emphasizes adherence to Apple Human Interface Guidelines and provides specific preferences for libraries and coding styles to ensure consistent development across Expo projects.
What it does
- Defines route conventions and project organization for Expo Router.
- Provides styling rules for native components, shadows, and text.
- Outlines navigation patterns including stacks, modals, and sheets.
- Details the use of native controls like switches, sliders, and date pickers.
- Offers guidance on implementing animations with Reanimated.
- Specifies library preferences and architectural patterns for Expo apps.
When to use it
- When starting a new Expo Router project and needing a standard structure.
- When implementing navigation stacks, modals, or native tabs.
- When styling components to align with Apple Human Interface Guidelines.
- When choosing between Expo Go and custom native builds.
- When integrating native UI controls or complex animations.
Advantages
- Promotes consistent code structure and file naming conventions.
- Encourages the use of native-first components and patterns.
- Provides clear guidance on when to use Expo Go versus custom builds.
- Reduces technical debt by recommending modern library alternatives.
- Ensures responsiveness through recommended layout practices.
Usage examples
- “How should I structure my navigation in an Expo Router app?”
- The skill provides a standard layout pattern using _layout.tsx files, NativeTabs, and shared group routes to organize stacks and tabs effectively.
- “What is the recommended way to handle shadows in my UI?”
- The guide instructs to use the CSS boxShadow style prop instead of legacy shadow or elevation styles, supporting both standard and inset shadows.
- “Should I use Expo Go or a custom build for my project?”
- The skill advises starting with Expo Go for most features and only creating custom builds when specific native modules, Apple targets, or custom native configurations are required.
Install
Download the skill as a ZIP:
https://autogineer.com/agent-skills/openai/building-native-ui/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip building-native-ui.zip -d .agents/skills
Documentation by Expo Team
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 GoCheck 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.tsxAlways 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-avexpo-videonotexpo-avexpo-imagewithsource="sf:name"for SF Symbols, notexpo-symbolsor@expo/vector-iconsreact-native-safe-area-contextnot react-native SafeAreaViewprocess.env.EXPO_OSnotPlatform.OSReact.usenotReact.useContextexpo-imageImage component instead of intrinsic elementimgexpo-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 insetscontentInsetAdjustmentBehavior="automatic"should be applied to FlatList and SectionList as wellUse 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/datetimepickerWhen a route belongs to a Stack, its first child should almost always be a ScrollView with
contentInsetAdjustmentBehavior="automatic"setWhen adding a
ScrollViewto the page it should almost always be the first component inside the route componentPrefer
headerSearchBarOptionsin Stack.Screen options to add a search barUse the
<Text selectable />prop on text containing data that could be copiedConsider 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 shapeALWAYS 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 messagesCounters 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 stacksUse 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>
);
}Files
- ATTRIBUTION.mdAdded by AutoGineer826 B
- LICENSELicence file added by AutoGineer1.1 KiB
- SKILL.md10.2 KiB
- agents/openai.yaml407 B
- references/animations.md4.6 KiB
- references/controls.md4.9 KiB
- references/form-sheet.md6.2 KiB
- references/gradients.md2.7 KiB
- references/icons.md4.7 KiB
- references/media.md6.1 KiB
- references/route-structure.md5.0 KiB
- references/search.md5.1 KiB
- references/storage.md2.9 KiB
- references/tabs.md13.0 KiB
- references/toolbar-and-headers.md7.9 KiB
- references/visual-effects.md4.2 KiB
- references/webgpu-three.md14.1 KiB
- references/zoom-transitions.md4.7 KiB
references/gradients.md
CSS Gradients
New Architecture Only: CSS gradients require React Native's New Architecture (Fabric). They are not available in the old architecture or Expo Go.
Use CSS gradients with the experimental_backgroundImage style property.
Linear Gradients
// Top to bottom
<View style={{
experimental_backgroundImage: 'linear-gradient(to bottom, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 1) 100%)'
}} />
// Left to right
<View style={{
experimental_backgroundImage: 'linear-gradient(to right, #ff0000 0%, #0000ff 100%)'
}} />
// Diagonal
<View style={{
experimental_backgroundImage: 'linear-gradient(45deg, #ff0000 0%, #00ff00 50%, #0000ff 100%)'
}} />
// Using degrees
<View style={{
experimental_backgroundImage: 'linear-gradient(135deg, transparent 0%, black 100%)'
}} />Radial Gradients
// Circle at center
<View style={{
experimental_backgroundImage: 'radial-gradient(circle at center, rgba(255, 0, 0, 1) 0%, rgba(0, 0, 255, 1) 100%)'
}} />
// Ellipse
<View style={{
experimental_backgroundImage: 'radial-gradient(ellipse at center, #fff 0%, #000 100%)'
}} />
// Positioned
<View style={{
experimental_backgroundImage: 'radial-gradient(circle at top left, #ff0000 0%, transparent 70%)'
}} />Multiple Gradients
Stack multiple gradients by comma-separating them:
<View style={{
experimental_backgroundImage: `
linear-gradient(to bottom, transparent 0%, black 100%),
radial-gradient(circle at top right, rgba(255, 0, 0, 0.5) 0%, transparent 50%)
`
}} />Common Patterns
Overlay on Image
<View style={{ position: 'relative' }}>
<Image source={{ uri: '...' }} style={{ width: '100%', height: 200 }} />
<View style={{
position: 'absolute',
inset: 0,
experimental_backgroundImage: 'linear-gradient(to top, rgba(0, 0, 0, 0.8) 0%, transparent 50%)'
}} />
</View>Frosted Glass Effect
<View style={{
experimental_backgroundImage: 'linear-gradient(135deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.05) 100%)',
backdropFilter: 'blur(10px)',
}} />Button Gradient
<Pressable style={{
experimental_backgroundImage: 'linear-gradient(to bottom, #4CAF50 0%, #388E3C 100%)',
padding: 16,
borderRadius: 8,
}}>
<Text style={{ color: 'white', textAlign: 'center' }}>Submit</Text>
</Pressable>Important Notes
Do NOT use
expo-linear-gradient— use CSS gradients insteadGradients are strings, not objects
Use
rgba()for transparency, ortransparentkeywordColor stops use percentages (0%, 50%, 100%)
Direction keywords:
to top,to bottom,to left,to right,to top left, etc.Degree values:
45deg,90deg,135deg, etc.
Requirements and compatibility
Written for: OpenAI Codex
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Needs: Needs Node.js
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- Expo Team
- Publisher
- OpenAI (Official)
- Distributed by
- OpenAI
- Source
- openai/plugins · plugins/expo/skills/building-native-ui · commit d420d13
- Licence
- MIT License (redistribution allowed)
- LICENSE (added by AutoGineer from plugins/expo/LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by Expo Team or OpenAI.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download building-native-ui.zip