expo-sdk
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:
- Create project:
npx create-expo-app [demo-name] --template blank-typescript - 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 - Configure NativeWind (see
nativewindskill) - Set up root layout with provider stack
- Configure
app.jsonwith scheme, name, splash - Add route groups and screens
- Run:
pnpm start(Expo dev server)
Adding a new library:
- Install with pnpm:
pnpm add [library] - Check if Expo config plugin needed in
app.json - 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)
GestureHandlerRootViewmust be outermost (required by gesture handler and bottom sheets)SafeAreaProviderprovides safe area insets to all descendants- App-level context providers go between SafeAreaProvider and Stack
<Stack screenOptions={{ headerShown: false }} />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 imagesplaceholder— blurhash string for loading statecontentFit—'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, togglesHaptics.impactAsync(ImpactFeedbackStyle.Medium)— button press, card tapHaptics.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:
backdropComponentwith press-to-dismiss BottomSheetScrollViewfor scrollable content inside sheets- Requires
GestureHandlerRootViewas ancestor
FlashList (replaces FlatList)
High-performance list rendering from @shopify/flash-list:
- Drop-in FlatList replacement with mandatory
estimatedItemSizeprop estimatedItemSize={80}— estimated height of each item in points- Recycling architecture for smooth 60fps scrolling
- Use
contentContainerClassNamefor 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:
<StatusBar style="dark" />for light backgrounds<StatusBar style="light" />for dark backgrounds- Import from
expo-status-bar
Best Practices
- Wrap root layout in
GestureHandlerRootViewwithstyle={{ flex: 1 }} - Use expo-image for all images (caching, blurhash, performance)
- Add haptics to primary actions only (buttons, major selections) — not every touch
- Set
estimatedItemSizeon all FlashList components - Place providers in root
_layout.tsx, not in individual screens - Use
useSafeAreaInsets()for manual padding,SafeAreaViewfor simple wrapping - Test on real device for haptics and performance verification
Anti-Patterns
- Using React Native
Imageinstead ofexpo-image - Using
FlatListfor large datasets instead ofFlashList - Forgetting
GestureHandlerRootView(causes bottom sheet and gesture crashes) - Overusing haptics on every interaction
- Hardcoding status bar height instead of using safe area insets
- Missing
estimatedItemSizeon FlashList (required prop, console warning) - Placing
SafeAreaViewinside ScrollView (causes layout issues) - Not including
expo-routerplugin inapp.jsonplugins array