Install
$ agentstack add skill-expo-skills-building-native-ui ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
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-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 backdropsColorfromexpo-routerfor native semantic colors, not rawPlatformColor(type-safe, auto-adapts to light/dark)- In SDK 56+, never import from
@react-navigation/*directly — useexpo-router/react-navigationinstead (covers@react-navigation/native,/core,/elements,/routers)
Responsiveness
- Always wrap root component in a scroll view for responsiveness
- Use `
instead of` 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 `
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 `` 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
Colors
Use the Color API from expo-router for native semantic colors. It is a type-safe wrapper over PlatformColor that exposes iOS UIKit colors through Color.ios.* and Android Material 3 colors through Color.android.material.* (static) or Color.android.dynamic.* (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 colors.web.ts file.
Color is platform-specific, so wrap each value in Platform.select with a default hex fallback for web. Centralize the palette in theme/colors.ts and import colors everywhere:
// 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",
})!,
};
import { colors } from "@/theme/colors";
Title
;
- iOS re-resolves these colors automatically when the system theme changes. On Android, call
useColorScheme()inside any component that renders them so it re-renders when the theme flips (required when React Compiler memoizes the component). - Don't pass
Color/PlatformColorvalues into Reanimated styles — use static colors there (seereferences/animations.md). Platform.select({...})!returnsstring | OpaqueColorValue. Most React Native style props acceptColorValue(string | OpaqueColorValue) so this works fine. But some third-party props only acceptstring(e.g.tintColoronexpo-image). Cast when needed:colors.label as string.
Text Styling
- Add the
selectableprop to every `` 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.
'inset' shadows are supported.
Navigation
Link
Use `` from 'expo-router' for navigation between routes.
import { Link } from 'expo-router';
// Basic link
// Wrapping custom components
...
Whenever possible, include a `` 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:
Context Menus
Add long press context menus to Link components:
import { Link } from "expo-router";
{}} />
{}}
/>
;
Link Previews
Use link previews frequently to enhance navigation:
Link preview can be used with context menus.
Modal
Present a screen as a modal:
Prefer this to building a custom modal component.
Sheet
Present a screen as a dynamic form sheet:
- 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 —
(index,search)/
_layout.tsx —
index.tsx — Main list
search.tsx — Search view
// app/_layout.tsx
import { NativeTabs } from "expo-router/unstable-native-tabs";
import { ThemeProvider, DarkTheme, DefaultTheme } from "expo-router/react-navigation";
import { useColorScheme } from "react-native";
export default function Layout() {
const colorScheme = useColorScheme();
return (
Items
);
}
Create a shared group route so both tabs can push common screens:
// app/(index,search)/_layout.tsx
import { Stack } from "expo-router/stack";
import { colors } from "@/theme/colors";
export default function Layout({ segment }) {
const screen = segment.match(/\((.*)\)/)?.[1]!;
const titles: Record = { index: "Items", search: "Search" };
return (
);
}
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: expo
- Source: expo/skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.