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 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:
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