Native UI
Verified@wpank
npx machina-cli add skill @wpank/native-ui --openclawNative 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 startand 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.jsonpath aliases; prefer aliases over relative imports
Routes
See ./references/route-structure.md for detailed conventions.
- Routes belong in the
appdirectory - 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
useWindowDimensionsoverDimensions.get()for screen measurement
Behavior
- Use
expo-hapticsconditionally 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
headerSearchBarOptionsin 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.createunless 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
contentContainerStylefor padding/gap (avoids clipping) - CSS and Tailwind are not supported — use inline styles
Text Styling
- Add
selectableprop 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.tsxfiles to define stacks - Use
Stackfromexpo-router/stackfor 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:
// 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>
);
}
Shared group layout:
// 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>
);
}
Overview
Build native mobile interfaces using Expo Router and React Native. The skill covers routing, navigation, styling, native controls, animations, and platform conventions aligned with Apple Human Interface Guidelines, enabling iOS-native experiences within Expo workflows.
How This Skill Works
Leverage the app/ routing structure with Expo Router, combine native controls (Switch, Slider, DateTimePicker) and iOS conventions, and enhance UI with Reanimated for transitions. Design patterns reference icons, visuals, and tabs, while responsive layout uses ScrollView and window dimensions to adapt across devices.
When to Use It
- Starting a new native mobile UI project with Expo Router and React Native.
- Implementing native iOS controls and animations (Switch, Slider, DateTimePicker).
- Creating a tabbed interface using NativeTabs with Apple-style conventions.
- Building responsive layouts that work across iPhone, iPad, and screen sizes.
- Following Apple Human Interface Guidelines for consistent UX and feel.
Quick Start
- Step 1: Install the skill package with npx clawhub@latest install native-ui
- Step 2: Start the app with Expo: npx expo start
- Step 3: Create routes in app/ and add a screen using native UI controls, then test in Expo Go
Best Practices
- Organize routes under the app/ directory with a clear hierarchy; avoid co-locating components in app/.
- Use NativeTabs for iOS tab bars and adhere to platform conventions.
- Wrap root components in ScrollView to improve responsiveness.
- Prefer useWindowDimensions over Dimensions.get() for layout calculations.
- Avoid deprecated modules (Picker, WebView, SafeAreaView from RN core) and minimize custom native code unless necessary.
Example Use Cases
- Onboarding flow with route groups and header/navigation patterns for a guided first-run experience.
- A settings screen using native controls like Switch and DateTimePicker with iOS-style styling.
- A tabbed home screen built with NativeTabs and SF Symbols icons for navigation cues.
- Animated screen transitions using Reanimated for entering/exiting views.
- Responsive dashboard that adapts to various form factors using ScrollView and useWindowDimensions.