--- name: expo-sdk description: Expo SDK 54+ platform patterns. Use when configuring Expo apps, setting up root layouts, using expo-image, expo-haptics, safe areas, bottom sheets, FlashList, or StatusBar. --- # Expo SDK ## Overview Expo SDK 54+ provides a managed React Native development environment with file-based routing (Expo Router), native module access, and streamlined build tooling. This skill covers app configuration, the root layout provider pattern, and key Expo/RN libraries. **Prerequisite**: `npx create-expo-app` or Expo SDK 54+ in `package.json` ## Workflows **Setting up a new Expo demo:** 1. [ ] Create project: `npx create-expo-app [demo-name] --template blank-typescript` 2. [ ] Install core dependencies: `pnpm add expo-router expo-image expo-haptics react-native-reanimated react-native-gesture-handler react-native-safe-area-context @gorhom/bottom-sheet @shopify/flash-list lucide-react-native nativewind tailwindcss@3` 3. [ ] Configure NativeWind (see `nativewind` skill) 4. [ ] Set up root layout with provider stack 5. [ ] Configure `app.json` with scheme, name, splash 6. [ ] Add route groups and screens 7. [ ] Run: `pnpm start` (Expo dev server) **Adding a new library:** 1. [ ] Install with pnpm: `pnpm add [library]` 2. [ ] Check if Expo config plugin needed in `app.json` 3. [ ] Rebuild dev client if native module added: `npx expo prebuild` ## Guidance ### app.json Configuration Key fields for demo apps: | Field | Purpose | |-------|---------| | `expo.name` | Display name | | `expo.slug` | URL-safe identifier | | `expo.scheme` | Deep link scheme (e.g., `myapp`) | | `expo.orientation` | `portrait` (default for demos) | | `expo.splash` | Splash screen configuration | | `expo.ios.bundleIdentifier` | iOS bundle ID | | `expo.android.package` | Android package name | | `expo.plugins` | Expo config plugins (e.g., `expo-router`) | ### Root Layout Provider Pattern The root `app/_layout.tsx` wraps the entire app with providers. Standard order: ``` GestureHandlerRootView (flex: 1) └── SafeAreaProvider └── ThemeProvider / Context └── Stack (Expo Router) ``` - `GestureHandlerRootView` must be outermost (required by gesture handler and bottom sheets) - `SafeAreaProvider` provides safe area insets to all descendants - App-level context providers go between SafeAreaProvider and Stack - `` for custom headers ### expo-image (replaces RN Image) Use `expo-image` for all image rendering — provides caching, blurhash placeholders, content-fit modes, and animated transitions. Key props: - `source` — URI string or require() for local images - `placeholder` — blurhash string for loading state - `contentFit` — `'cover'` | `'contain'` | `'fill'` - `transition` — fade-in duration in ms (e.g., `300`) ### expo-haptics Provide tactile feedback on interactions: - `Haptics.selectionAsync()` — light tap for selections, toggles - `Haptics.impactAsync(ImpactFeedbackStyle.Medium)` — button press, card tap - `Haptics.notificationAsync(NotificationFeedbackType.Success)` — action completion Use sparingly — haptics on every touch is annoying. ### Safe Area Insets Account for device notch, status bar, and home indicator: - `useSafeAreaInsets()` — returns `{ top, bottom, left, right }` in points - Apply to screen containers: `paddingTop: insets.top` - NativeWind classes: use `pt-[${insets.top}px]` or wrap in SafeAreaView ### @gorhom/bottom-sheet Replaces Radix Dialog for mobile modal patterns: - Use for detail views, selections, filters, forms - Define snap points: `snapPoints={['25%', '50%', '90%']}` - Backdrop: `backdropComponent` with press-to-dismiss - `BottomSheetScrollView` for scrollable content inside sheets - Requires `GestureHandlerRootView` as ancestor ### FlashList (replaces FlatList) High-performance list rendering from `@shopify/flash-list`: - Drop-in FlatList replacement with mandatory `estimatedItemSize` prop - `estimatedItemSize={80}` — estimated height of each item in points - Recycling architecture for smooth 60fps scrolling - Use `contentContainerClassName` for NativeWind styling ### lucide-react-native Icon library for React Native (matches web lucide-react): - Import individual icons: `import { Home, Settings, ChevronRight } from 'lucide-react-native'` - Props: `size`, `color`, `strokeWidth` - Consistent icon set across mobile and web codebases ### StatusBar Configure status bar appearance per screen: - `` for light backgrounds - `` for dark backgrounds - Import from `expo-status-bar` ## Best Practices - Wrap root layout in `GestureHandlerRootView` with `style={{ flex: 1 }}` - Use expo-image for all images (caching, blurhash, performance) - Add haptics to primary actions only (buttons, major selections) — not every touch - Set `estimatedItemSize` on all FlashList components - Place providers in root `_layout.tsx`, not in individual screens - Use `useSafeAreaInsets()` for manual padding, `SafeAreaView` for simple wrapping - Test on real device for haptics and performance verification ## Anti-Patterns - Using React Native `Image` instead of `expo-image` - Using `FlatList` for large datasets instead of `FlashList` - Forgetting `GestureHandlerRootView` (causes bottom sheet and gesture crashes) - Overusing haptics on every interaction - Hardcoding status bar height instead of using safe area insets - Missing `estimatedItemSize` on FlashList (required prop, console warning) - Placing `SafeAreaView` inside ScrollView (causes layout issues) - Not including `expo-router` plugin in `app.json` plugins array