expo-ui
Build native UI with @expo/ui: real SwiftUI on iOS and Jetpack Compose on Android. Use for sheets, pickers, sliders, toggles, menus and grouped form sections, with drop-in replacements for React Native community libraries.
Install
npx skills add https://github.com/expo/skills --skill expo-uiPlatform-specific iOS UI: @expo/ui/swift-ui
iOS only. Code that imports from
@expo/ui/swift-uiwill crash on Android with "Unable to get view config" errors. Always place this code in an.ios.tsxcomponent file or guard it withPlatform.OS === 'ios'.Hostmust be imported from@expo/ui(the universal root), not from@expo/ui/swift-ui.
Use this layer only when the universal @expo/ui components don't cover what you need on iOS (see ./universal.md first). This requires a platform-specific tree.
File placement with Expo Router
Do not put .ios.tsx files inside app/ or src/app/. Expo Router does not support platform-extension suffixes for route files and will throw a "no fallback sibling" Render Error.
Place platform-specific component files in components/ (or any directory outside the route tree), then import them from a regular route file:
src/app/profile-editor.tsx:
Alternatively, keep everything in one regular route file and branch on Platform.OS:
Or more simply, put the Platform.OS guard and the SwiftUI import in the same route file (safe because Metro only bundles .ios.tsx imports on iOS builds when using platform extensions in components/).
Instructions
- Expo UI's API mirrors SwiftUI's API. Use SwiftUI knowledge to decide which components or modifiers to use.
- Components are imported from
@expo/ui/swift-ui, modifiers from@expo/ui/swift-ui/modifiers. - Before writing any code, run the list-components script to get the exact components and modifiers available in the installed version:
(
<skill-root>is the directory containing thisreferences/folder.) - The installed package's TypeScript types (
.d.ts) are the most reliable source of truth for prop shapes and signatures — read the relevant{Component}/index.d.tsfrom the installed@expo/ui/swift-uipackage innode_modules. Use the docs below as the human-readable reference. - When about to use a component, fetch its docs to confirm the API — https://docs.expo.dev/versions/latest/sdk/ui/swift-ui/{component-name}/index.md
- When unsure about a modifier's API, refer to the docs — https://docs.expo.dev/versions/latest/sdk/ui/swift-ui/modifiers/index.md
- Every SwiftUI tree must be wrapped in
Host. RNHostViewis specifically for embedding RN components inside a SwiftUI tree. Example:
- If a required modifier or View is missing in Expo UI, it can be extended via a local Expo module. See: https://docs.expo.dev/guides/expo-ui-swift-ui/extending/index.md. Confirm with the user before extending.
useNativeState
useNativeState creates observable state that updates synchronously on the UI thread via worklets, enabling immediate native state changes without waiting for a React render cycle. Requires react-native-worklets — without it updates still go through React and flickering remains. Best for real-time interactions where synchronous updates matter, e.g. a text field that masks or formats input as the user types.
ObservableState.valueis readable/writable from worklets;onChangefires a worklet listener on state change.- Docs — https://docs.expo.dev/versions/latest/sdk/ui/swift-ui/usenativestate/index.md