Ai native-ui
install
source · Clone the upstream repo
git clone https://github.com/wpank/ai
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/wpank/ai "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/frontend/native-ui" ~/.claude/skills/wpank-ai-native-ui && rm -rf "$T"
manifest:
skills/frontend/native-ui/SKILL.mdsource content
Native UI with Expo Router
Patterns and conventions for building native mobile applications with Expo Router and React Native.
References
Consult these as needed:
— Route conventions, dynamic routes, groups, folder organization./references/route-structure.md
— Native tab bar with NativeTabs, iOS 26 features./references/tabs.md
— SF Symbols with expo-symbols, icon names, animations, weights./references/icons.md
— Native iOS controls: Switch, Slider, SegmentedControl, DateTimePicker./references/controls.md
— Blur effects (expo-blur) and liquid glass (expo-glass-effect)./references/visual-effects.md
— Reanimated: entering, exiting, layout, scroll-driven, gestures./references/animations.md
— Search bar with headers, useSearch hook, filtering patterns./references/search.md
— CSS gradients via experimental_backgroundImage (New Architecture only)./references/gradients.md
— Camera, audio, video, file saving./references/media.md
— SQLite, AsyncStorage, SecureStore./references/storage.md
— 3D graphics and GPU visualizations with WebGPU/Three.js./references/webgpu-three.md
— Stack headers and toolbar with buttons, menus, search bars (iOS)./references/toolbar-and-headers.md
— Form sheet presentation patterns./references/form-sheet.md
Running the App
Always try Expo Go first before creating custom builds.
- Start with
and scan the QR codenpx expo start - 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
path aliases; prefer aliases over relative importstsconfig.json
Routes
See
./references/route-structure.md for detailed conventions.
- Routes belong in the
directoryapp - Never co-locate components, types, or utilities in
— this is an anti-patternapp/ - Always have a route matching
, possibly inside a group route/
Library Preferences
| Use | Instead of |
|---|---|
| |
| |
| |
| RN SafeAreaView |
| |
| |
| Intrinsic element |
| 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
instead of<ScrollView contentInsetAdjustmentBehavior="automatic" /><SafeAreaView> - Apply
to FlatList and SectionList toocontentInsetAdjustmentBehavior="automatic" - Use flexbox instead of Dimensions API
- Prefer
overuseWindowDimensions
for screen measurementDimensions.get()
Behavior
- Use
conditionally on iOS for delightful interactionsexpo-haptics - 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
in Stack.Screen options for search barsheaderSearchBarOptions - Use
on data that users may want to copy<Text selectable /> - Format large numbers: 1.4M, 38k
- Never use intrinsic elements (
,img
) outside webviews or Expo DOM componentsdiv
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
unless reusing stylesStyleSheet.create - Add entering/exiting animations for state changes
- Use
for rounded corners (not capsule shapes){ borderCurve: 'continuous' } - Use navigation stack title instead of custom text headers
- On ScrollView, use
for padding/gap (avoids clipping)contentContainerStyle - CSS and Tailwind are not supported — use inline styles
Text Styling
- Add
prop toselectable
elements showing important data or errors<Text/> - Use
on counters for alignment{ fontVariant: 'tabular-nums' }
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
files to define stacks_layout.tsx - Use
fromStack
for native navigation stacksexpo-router/stack - 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> ); }