Mobile App Patterns
React Native and Expo mobile UI guidance. Read this reference when building or improving mobile app screens.
Safe Areas
- Always wrap root layout in
SafeAreaView(React Native) or useuseSafeAreaInsets()fromreact-native-safe-area-context. - Apply top inset to headers, bottom inset to tab bars and floating action buttons.
- Do not hardcode status bar height — varies across devices (especially iPhone notch/Dynamic Island, Android camera cutouts).
import { useSafeAreaInsets } from 'react-native-safe-area-context';
function Screen() {
const insets = useSafeAreaInsets();
return (
<View style={{ paddingTop: insets.top, paddingBottom: insets.bottom }}>
{/* content */}
</View>
);
}Thumb Zones
- Place primary actions in bottom third of screen (easy thumb reach).
- Navigation bars at bottom, not top.
- Avoid placing critical actions in top corners — hard to reach one-handed.
- Pull-to-refresh for content lists.
- Swipe actions (delete, archive) on list items for quick actions.
┌─────────────────┐
│ Hard reach │ ← Secondary info, settings
│ │
│ Comfortable │ ← Content, scrollable area
│ │
│ Easy reach │ ← Primary actions, nav bar
└─────────────────┘Tab Bars
- 3–5 tabs maximum. More than 5 → use "More" tab or reorganize.
- Each tab has icon + label. Icon-only tabs are ambiguous.
- Active tab: filled icon or color change + label highlight.
- Tab bar always visible on main screens. Hide during immersive experiences (video, reading).
- Tapping active tab scrolls content to top (convention on both iOS and Android).
Stack Navigation
- Use stack navigator for drill-down flows (list → detail → edit).
- Back button always present on pushed screens.
- Screen transitions: slide-in from right (iOS), fade/slide-up (Android/Material).
- Reset stack on tab switch — user returns to root of each tab.
- Deep linking: map URL paths to screen names for universal links.
Tab: Home
├── HomeScreen (root)
├── ItemDetailScreen (push)
└── EditItemScreen (push)Touch Targets
- Minimum touch target: 44×44 points (Apple HIG) / 48×48 dp (Material).
- Add
hitSlopfor elements visually smaller than 44px. - Spacing between adjacent targets: minimum 8px to prevent mis-taps.
- Interactive text links: increase tap area with padding.
- Provide visual feedback on press: opacity change (
activeOpacity={0.7}) or highlight.
<Pressable
hitSlop={8}
style={({ pressed }) => [styles.button, pressed && { opacity: 0.7 }]}
>
<Text>Action</Text>
</Pressable>Platform Conventions
iOS
- Use SF Pro or system font. Do not override system font unless brand requires it.
- Navigation: large title headers that collapse on scroll.
- Modals: present as card (pageSheet) with swipe-to-dismiss.
- Destructive actions: red text in action sheets.
- Haptic feedback for significant actions (
expo-haptics). - Avoid Android-style FABs (floating action buttons) — use navigation bar buttons.
Android (Material)
- Use Roboto or system font.
- Navigation: top app bar with hamburger or back arrow.
- FAB for primary screen action.
- Bottom sheets for secondary options.
- Snackbar for brief status messages (not iOS-style alerts).
- Material ripple effect on touch (
android_ripple).
Cross-Platform Harmony
- Use
Platform.select()orPlatform.OSfor per-platform styling when conventions diverge. - Prefer shared behavior, platform-specific polish.
- Test on both iOS simulator and Android emulator.
- Use Expo for rapid iteration —
expo-routerfor file-based navigation.
React Native / Expo Notes
- Expo SDK: prefer Expo-managed workflow for faster iteration. Eject to bare only when native module requires it.
- expo-router: file-based routing. Layouts define tab/stack structure. Recommended for new projects.
- Styling:
StyleSheet.create()for performance. Avoid inline style objects in render. - Lists: use
FlashList(from@shopify/flash-list) for long lists.FlatListfor shorter lists. Never useScrollViewwith.map()for dynamic lists. - Images: use
expo-imagefor performant image loading with caching, blur hash placeholders. - Fonts: load custom fonts with
expo-fontanduseFonts()hook. Show splash screen until fonts loaded. - Icons:
@expo/vector-iconsbundles popular icon sets. Use consistently sized icons (20–24px).