Choose native UI when platform fidelity is more important than identical cross-platform rendering. Expo UI exposes universal components backed by SwiftUI on iOS and Jetpack Compose on Android, with platform-specific escape hatches when a shared abstraction stops fitting.
Choose a Rendering Lane
- Use React Native components for highly branded, cross-platform surfaces.
- Use Expo UI universal components for settings, forms, lists, menus, controls, and other platform-shaped surfaces.
- Use platform files when the interaction is conceptually shared but its implementation should differ.
- Use direct SwiftUI or Jetpack Compose bindings for a platform-exclusive capability or modifier.
If a design requires continuously fighting a universal component's native behavior, it is probably in the wrong lane.
Adopt the Native Layout Model
Universal native trees live inside Host. Compose them with Column, Row, and Spacer, not React Native Flexbox assumptions. A Host can carry environment such as color scheme and Android seed color.
SwiftUI and Compose share useful concepts but not identical APIs:
| Intent | SwiftUI | Compose |
|---|---|---|
| Vertical composition | VStack | Column |
| Horizontal composition | HStack | Row |
| Layering | ZStack | Box |
| Transformation | modifier chain | modifier chain |
Modifier order can change layout, clipping, background, and interaction. Treat the chain as execution order, not a bag of CSS properties.
Use a Styling Escape Ladder
Start with the smallest mechanism that preserves the universal contract:
- Universal style props for basic dimensions and spacing.
- Text-specific styling for typography.
- Native modifiers for platform behavior and appearance.
.ios.tsxand.android.tsximplementations behind one typed interface.- A custom native view or module only when necessary.
Restart Metro after adding a new platform-specific file if resolution does not update.
Five Familiar List Interactions
Use these patterns to reduce repeated work. Enter a clear selection or editing mode before changing what a row tap does.
1. Select Multiple Items
When the same action applies to several items, let people select them once and act in bulk. This works well for deleting, archiving, marking as read, completing, moving, or sharing.
- Provide an explicit Select or Edit action instead of changing row behavior without warning.
- Show a clear selected state on every row and keep the selection count visible.
- Let people cancel selection mode without changing the items.
- Keep item identifiers stable if the list refreshes while selection is active.
On iOS, Expo UI's SwiftUI List supports controlled selection in edit mode. Treat this as platform-specific behavior and design the equivalent Android flow deliberately.
2. Show Contextual Actions
Make bulk actions contextual. When one or more items are selected, reveal actions such as Complete, Archive, or Delete. Hide or disable them when the selection is empty.
Keep the toolbar stable while the selection changes, show only actions that apply to every selected item, and separate destructive actions from routine ones. Expo Router's native Stack.Toolbar can show and hide items from screen state, but it is an alpha API. Check the installed Expo SDK before using it, and provide another toolbar implementation where it is unavailable.
3. Make Swipes Shortcuts
People often try swiping rows in a mobile list. Use swipe actions for one or two frequent, easy-to-understand actions such as archive, complete, mark as read, or delete.
- Keep the primary row tap unchanged.
- Use labels and familiar icons; do not rely on color alone.
- Provide the same actions through a visible control, contextual menu, or screen-reader action because gestures cannot be the only path.
- Make a destructive full swipe reversible with Undo, or require a second commitment.
- Test the gesture against vertical scrolling, back gestures, and right-to-left layouts.
Expo UI's SwiftUI SwipeActions is an iOS component. Use a platform-specific Android implementation instead of assuming one component behaves identically on both platforms. For custom gestures, follow the animation-and-gestures guide.
4. Reorder Personal Lists
Offer drag-to-reorder when the order belongs to the person using the app: habits, tasks, playlists, routines, shortcuts, or favorites. Do not add manual sorting when the product has one meaningful order, such as chronological messages.
Use an explicit edit mode or drag handle when accidental movement would be costly. Show where the item will land, persist the new order after the drop, and provide accessible Move up and Move down actions. Do not put a reorder gesture and a different long-press action on the same target.
Expo UI's SwiftUI List.ForEach supports onMove for iOS lists. Check the installed SDK and build an equivalent Android interaction when the feature is shared.
5. Use Native Form Controls
Use platform controls for settings, item creation, and other familiar forms: switches, pickers, menus, color pickers, text fields, and grouped form sections. Native controls already match many platform expectations for focus, text scaling, keyboard behavior, and assistive technology.
Keep custom UI for interactions that express the product's distinct value. Even with native controls, provide clear labels, help text, validation, and errors, and test every target platform instead of treating native behavior as automatic accessibility.
Keep High-Frequency State Native
Text input selection, gesture progress, and other per-frame state should not round-trip through React state unless React must render from it. Native state and worklets can keep interaction responsive, then commit meaningful state at boundaries such as submit, blur, or gesture completion.
Do not create two competing sources of truth. Document which layer owns live state and when it synchronizes with application state.
Platform Quality Checklist
- Dynamic type and screen-reader labels work.
- Every gesture action has a visible or accessible alternative.
- Selection mode shows its state, count, available actions, and a clear exit.
- Reordering has an accessible alternative and does not fight scrolling.
- Destructive actions require confirmation or provide a reliable undo.
- Touch targets follow platform guidance: aim for 44 by 44 points on iOS and at least 48 by 48 dp on Android. Expanded targets do not overlap.
- Android uses Material 3 conventions where applicable.
- iOS and Android each have appropriate navigation and control behavior.
- Safe-area and keyboard behavior is tested inside the host.
- A shared abstraction does not erase meaningful platform differences.
Sources
Check the current Expo UI overview, universal components, SwiftUI List, SwipeActions, native state, and Expo Router Stack Toolbar documentation because these APIs are evolving. The interaction guidance also follows Apple's current lists and tables and gesture conventions and Android's accessibility guidance.