| name | native-ui |
| model | standard |
| version | 1.0.0 |
| description | Building native mobile UIs with Expo Router and React Native. Covers routing, navigation, styling, native controls, animations, and platform conventions following Apple Human Interface Guidelines.
|
| tags | ["expo","react-native","ios","mobile","navigation","native-ui"] |
Native UI with Expo Router
Patterns and conventions for building native mobile applications with Expo Router and React Native.
References
Consult these as needed:
./references/route-structure.md — Route conventions, dynamic routes, groups, folder organization
./references/tabs.md — Native tab bar with NativeTabs, iOS 26 features
./references/icons.md — SF Symbols with expo-symbols, icon names, animations, weights
./references/controls.md — Native iOS controls: Switch, Slider, SegmentedControl, DateTimePicker
./references/visual-effects.md — Blur effects (expo-blur) and liquid glass (expo-glass-effect)
./references/animations.md — Reanimated: entering, exiting, layout, scroll-driven, gestures
./references/search.md — Search bar with headers, useSearch hook, filtering patterns
./references/gradients.md — CSS gradients via experimental_backgroundImage (New Architecture only)
./references/media.md — Camera, audio, video, file saving
./references/storage.md — SQLite, AsyncStorage, SecureStore
./references/webgpu-three.md — 3D graphics and GPU visualizations with WebGPU/Three.js
./references/toolbar-and-headers.md — Stack headers and toolbar with buttons, menus, search bars (iOS)
./references/form-sheet.md — Form sheet presentation patterns
Running the App
Always try Expo Go first before creating custom builds.
- Start with
npx expo start and scan the QR code
- Test features in Expo Go
- Only create custom builds when required
When Custom Builds Are Required
Use npx expo run:ios/android or eas build only for:
- Local Expo modules (custom native code in
modules/)
- Apple targets (widgets, app clips via
@bacons/apple-targets)
- Third-party native modules not in Expo Go
- Custom native configuration beyond
app.json
Expo Go supports all expo-* packages, Expo Router, Reanimated, Gesture Handler, push notifications, and deep links out of the box.
Installation
OpenClaw / Moltbot / Clawbot
npx clawhub@latest install native-ui
Code Style
- Escape nested backticks and quotes correctly
- Always use import statements at the top of the file
- Use kebab-case for file names:
comment-card.tsx
- Remove old route files when restructuring navigation
- No special characters in file names
- Configure
tsconfig.json path aliases; prefer aliases over relative imports
Routes
See ./references/route-structure.md for detailed conventions.
- Routes belong in the
app directory
- Never co-locate components, types, or utilities in
app/ — this is an anti-pattern
- Always have a route matching
/, possibly inside a group route
Library Preferences
| Use | Instead of |
|---|
expo-audio | expo-av |
expo-video | expo-av |
expo-symbols | @expo/vector-icons |
react-native-safe-area-context | RN SafeAreaView |
process.env.EXPO_OS | Platform.OS |
React.use | React.useContext |
expo-image | Intrinsic img element |
expo-glass-effect | Custom glass backdrops |
Never use deprecated modules: Picker, WebView, SafeAreaView, AsyncStorage (from RN core), or legacy expo-permissions.
Responsiveness
- Wrap root components in a scroll view
- Use
<ScrollView contentInsetAdjustmentBehavior="automatic" /> instead of <SafeAreaView>
- Apply
contentInsetAdjustmentBehavior="automatic" to FlatList and SectionList too
- Use flexbox instead of Dimensions API
- Prefer
useWindowDimensions over Dimensions.get() for screen measurement
Behavior
- Use
expo-haptics conditionally on iOS for delightful interactions
- Use views with built-in haptics (
<Switch />, @react-native-community/datetimepicker)
- First child of a Stack route should almost always be a ScrollView with
contentInsetAdjustmentBehavior="automatic"
- Prefer
headerSearchBarOptions in Stack.Screen options for search bars
- Use
<Text selectable /> on data that users may want to copy
- Format large numbers: 1.4M, 38k
- Never use intrinsic elements (
img, div) outside webviews or Expo DOM components
Styling
Follow Apple Human Interface Guidelines.
General Rules
- Prefer flex gap over margin and padding
- Prefer padding over margin
- Always account for safe area via stack headers, tabs, or
contentInsetAdjustmentBehavior="automatic"
- Ensure both top and bottom safe area insets are handled
- Inline styles preferred over
StyleSheet.create unless reusing styles
- Add entering/exiting animations for state changes
- Use
{ borderCurve: 'continuous' } for rounded corners (not capsule shapes)
- Use navigation stack title instead of custom text headers
- On ScrollView, use
contentContainerStyle for padding/gap (avoids clipping)
- CSS and Tailwind are not supported — use inline styles
Text Styling
- Add
selectable prop to <Text/> elements showing important data or errors
- Use
{ fontVariant: 'tabular-nums' } on counters for alignment
Shadows
Use CSS boxShadow style prop. Never use legacy RN 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.
import { Link } from 'expo-router';
<Link href="/path" />
<Link href="/path" asChild>
<Pressable>...</Pressable>
</Link>
Include <Link.Preview> whenever possible to follow iOS conventions. Add context menus and previews frequently.
Stack
- Always use
_layout.tsx files to define stacks
- Use
Stack from expo-router/stack for native navigation stacks
- Set page titles in Stack.Screen options:
options={{ title: "Home" }}
Context Menus
Add long-press context menus to Link components:
<Link href="/settings" asChild>
<Link.Trigger>
<Pressable><Card /></Pressable>
</Link.Trigger>
<Link.Menu>
<Link.MenuAction title="Share" icon="square.and.arrow.up" onPress={handleShare} />
<Link.MenuAction title="Block" icon="nosign" destructive onPress={handleBlock} />
<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
<Link href="/settings">
<Link.Trigger>
<Pressable><Card /></Pressable>
</Link.Trigger>
<Link.Preview />
</Link>
Can be combined with context menus.
Modal
Present a screen as a modal:
<Stack.Screen name="modal" options={{ presentation: "modal" }} />
Prefer this over custom modal components.
Sheet
Present as a dynamic form sheet:
<Stack.Screen
name="sheet"
options={{
presentation: "formSheet",
sheetGrabberVisible: true,
sheetAllowedDetents: [0.5, 1.0],
contentStyle: { backgroundColor: "transparent" },
}}
/>
contentStyle: { backgroundColor: "transparent" } enables liquid glass on iOS 26+.
Common Route Structure
Standard app layout with tabs and stacks:
app/
_layout.tsx — <NativeTabs />
(index,search)/
_layout.tsx — <Stack />
index.tsx — Main list
search.tsx — Search view
Root layout:
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>
);
}
Shared group layout:
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>
);
}