expo-mobile
>
Install
mkdir -p .claude/skills/expo-mobile && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/15536" && unzip -o skill.zip -d .claude/skills/expo-mobile && rm skill.zipInstalls to .claude/skills/expo-mobile
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Expo and React Native patterns for Halvy. Triggers: "component", "screen", "navigation", "expo", "NativeWind", "mobile", "React Native".About this skill
Expo / React Native Development — Halvy Patterns
Project Setup
- Expo SDK managed workflow with Expo Router v3
- NativeWind v4 (Tailwind CSS syntax)
- Supabase backend
- TypeScript strict mode
- FlashList (not FlatList) for all scrollable lists
Explicit Dependencies (SDK 54 — must be listed, never assume transitive)
These are NOT guaranteed as transitive installs — add them explicitly in package.json:
babel-preset-expoas a devDependency (pin to~54.0.xmatching SDK version)react-native-worklets— required peer dep of[email protected]; install vianpx expo install react-native-workletsreact-native-web,react-dom,@expo/metro-runtime— required for web platform support
babel.config.js — Critical Pattern
nativewind/babel returns { plugins: [...] } — it is a preset, not a plugin.
Place it in presets[] or Metro will throw: .plugins is not a valid Plugin property
// CORRECT
presets: ['babel-preset-expo', 'nativewind/babel']
plugins: [['module-resolver', { alias: { '@': './src' } }]]
// WRONG — causes silent build failures
plugins: ['nativewind/babel']
Build Verification
tsc and jest do NOT validate Metro/Babel transforms. Always run before merging:
npx expo export --platform ios
npx expo export --platform android
npx expo export --platform web
Zero test files → jest exits 0. This is NOT a build confirmation.
Feature File Structure
src/features/{name}/ components/ — Feature-specific UI components hooks/ — TanStack Query hooks and local state hooks utils/ — Pure functions (no React) tests/ — Integration tests
Critical Rules
- No window or document — not available in React Native
- No div/span/p — use View, Text, Pressable from react-native
- Always handle keyboard avoidance on input screens
- Always handle safe area insets
- Always use deep linking for navigable screens
- Use expo-image over Image for performance
- FlashList over FlatList for long lists — always
- All monetary amounts in integer cents — never floats
NativeWind Rules
- className with Tailwind utilities on all RN components
- Platform-specific: className="ios:pt-2 android:pt-4"
- Dark mode: className="bg-white dark:bg-gray-900"
- Never mix StyleSheet.create with className in the same component
- Use design tokens from tailwind.config.ts
Screen Pattern
import { ScrollView } from 'react-native' import { Stack } from 'expo-router' import { ErrorBoundary } from '@/components/ErrorBoundary'
export default function FeatureScreen() { return ( <ErrorBoundary> <Stack.Screen options={{ title: 'Feature' }} /> <ScrollView className="flex-1 bg-background"> {/* content */} </ScrollView> </ErrorBoundary> ) }
Supabase Realtime Pattern (Chat)
useEffect(() => { const channel = supabase .channel('chat:' + groupId) .on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'messages', filter: 'group_id=eq.' + groupId, }, handleNewMessage) .subscribe() return () => { supabase.removeChannel(channel) } }, [groupId])
Monetary Arithmetic
// ALWAYS: convert to cents on entry, format on display only const amountCents = Math.round(parseFloat(input) * 100) // NEVER: parseFloat('1.10') + parseFloat('2.20') — float errors