Deft
Ask AI

Search

Search skills, authors and topics

expo / skills

expo-web-to-native

Migrate a web React app (Next.js, Vite, CRA) to a native iOS and Android app with Expo, mapping web idioms like the DOM, CSS, React Router and localStorage to native.

Install

npx skills add https://github.com/expo/skills --skill expo-web-to-native

Native patterns: redesigning web UX for native

Disclosed reference for expo-web-to-native, step 4. false-friends.md translates idioms (div → View); this translates UX patterns - a web interaction into its native equivalent. Step 4 isn't a port, it's this redesign.

Reach for @expo/ui first. It renders real SwiftUI (iOS) and Jetpack Compose (Android), so its components look and feel exactly like the OS — the difference between "native-ish RN" and "indistinguishable from an app Apple/Google shipped." See the expo-ui skill. Drop to styled RN primitives only for what @expo/ui doesn't cover: custom layouts (chat bubbles, bespoke cards) and large data lists (@expo/ui List is a JS-thread node per item — use FlashList/FlatList for feeds). @expo/ui runs in Expo Go (SDK 56+) — no dev build needed; reach for a dev build (the expo-dev-client skill) only for custom native modules.

Web patternNative redesign — reach for firstWhy
Top tab bar / nav linksNativeTabs - bottom, liquid glass on iOS 26 (expo-router)Thumb-reachable, OS-native bar
Page header / breadcrumblarge-title Stack header + header search field (expo-router)The native screen frame
In-page tabs / toggle groupSegmentedControl — @expo/ui (community/segmented-control)Native segmented switch
Modal / dialogBottomSheet — @expo/uiSheets are the native modal
<select> / dropdownPicker, or long-press MenuView — @expo/ui (community/menu)Native wheel / menu, no popover
Accordion / "show more"Collapsible — @expo/uiNative disclosure
Settings / short listList + FieldGroup rows (Switch / Picker / TextInput) — @expo/uiThe Settings-app look
Data feed / table / long listFlashList / FlatList, virtualized + momentumTables don't exist on mobile; @expo/ui List isn't for big data
Checkbox / toggleSwitch (on/off) or Checkbox (multi-select) — @expo/uiNative control, not a styled box
Date / time inputDateTimePicker — @expo/ui (community/datetime-picker)Native wheel / calendar
Range sliderSlider — @expo/ui
Onboarding / carousel / swipe pagesPagerView — @expo/ui (community/pager-view)Native paging, not a scroll strip
Refresh button / auto-pollpull-to-refresh (RefreshControl)The native refresh gesture
Hover menu / tooltiplong-press MenuView context menu — @expo/ui (community/menu)No hover on touch
Toast / snackbarnative banner + haptic (expo-haptics)
Buttons@expo/ui Button; Pressable for customNative press + haptics
Multi-column layoutsingle column + tabs / stackOne thing at a time on a phone

The test: if a screenshot of the native screen could pass for the web version, you reskinned it. Redesign until it could pass for a screen the OS shipped.

Feel — beyond the components

The table gets the right components; "native" also lives in motion and touch - and a web port arrives with almost none, so you must add it. The tooling is in expo-router (navigation/transitions) and expo-native-ui (motion/effects); reach for it, don't hand-roll:

  • Transitions for free - use Expo Router's native-stack so push/pop, modals, and sheets animate with real platform physics; shared-element zoom via expo-router zoom-transitions.md.
  • Motion - Reanimated (withSpring/withTiming) + react-native-gesture-handler for swipes/drags → load the expo-animation skill (purpose gate, timing values, interruption).
  • Touch — expo-haptics on commits, selection changes, and pull-to-refresh — choreography, not just a toast buzz.
  • Native rhythm — large-title collapse on scroll, momentum / inverted scroll, a keyboard that pushes content (KeyboardAvoidingView).
  • Respect reduced motion — gate non-essential animation on Reanimated's useReducedMotion().

Feel can't be screenshotted. A janky transition or wrong easing passes a still-image parity check and still betrays the app — for screens with motion, verify with a short recording (iOS xcrun simctl io booted recordVideo feel.mov; Android adb shell screenrecord; or a device-agent flow), not just a screenshot.