Expo UI Native
Declarative SwiftUI, Jetpack Compose, Universal API, and production rollout patterns.
Key takeaways
- In SDK 56 the SwiftUI and Jetpack Compose APIs are stable, and the Universal
@expo/uiAPI lets one codebase target Android, iOS, and web. - Write common screens with the Universal API and its
Hostroot first; drop to@expo/ui/swift-uior@expo/ui/jetpack-composeonly for platform-specific behavior. - Several community libraries (bottom-sheet, slider, picker, pager) have drop-in candidates, but they are not guaranteed prop-level replacements.
- Use
react-native-workletsanduseNativeStatefor high-frequency UI where JS round trips would be visible. - Expo UI components live in a native tree, so avoid
measure()/onLayoutand validate accessibility and Dynamic Color on real devices.
Overview
Expo UI lets React code declare native SwiftUI on iOS and Jetpack Compose on Android. In SDK 56,
SwiftUI and Jetpack Compose APIs are stable, and Universal components let teams write shared
Android, iOS, and web UI through @expo/ui.
SDK 56 status
- SwiftUI support: stable
- Jetpack Compose support: stable
- Universal API: shared imports from
@expo/ui - Expo Go support: included in the new default template
- AI support: Expo Skills and Expo MCP help agents stay aligned with current APIs
Universal Components First
For SDK 56, write common screens with the Universal API first. Drop down to @expo/ui/swift-ui or
@expo/ui/jetpack-compose only when the platform-specific behavior matters.
import { Button, Column, Host, Switch, Text } from '@expo/ui';
export default function SettingsScreen() {
return (
<Host style={{ flex: 1 }}>
<Column spacing={12} alignment="center">
<Text>Notification settings</Text>
<Switch label="Enable notifications" value={enabled} onValueChange={setEnabled} />
<Button label="Save" onPress={handleSave} />
</Column>
</Host>
);
}Host is the root for a Universal Expo UI subtree. It connects to native hosts on Android and iOS,
and falls back to React Native views on web.
Platform-specific APIs
Use platform-specific packages when a screen needs native modifiers, native state, or controls not covered by the Universal API.
import { Platform } from 'react-native';
const NativeSettings = Platform.select({
ios: () => require('@expo/ui/swift-ui').Form,
android: () => require('@expo/ui/jetpack-compose').Column,
default: () => require('@expo/ui').Column,
})();| Package | Use when |
|---|---|
@expo/ui | shared form, control, sheet, text, and layout UI |
@expo/ui/swift-ui | iOS system forms, modifiers, and SwiftUI-specific controls |
@expo/ui/jetpack-compose | Material 3, Dynamic Color, and Compose modifiers |
Drop-in Replacement Candidates
SDK 56 provides import-oriented migration paths for several community native UI libraries. Treat them as drop-in candidates, not guaranteed prop-level replacements.
| Existing library | Expo UI direction |
|---|---|
@gorhom/bottom-sheet | Expo UI BottomSheet |
@react-native-community/datetimepicker | Expo UI date/time controls |
@react-native-community/slider | Expo UI Slider |
@react-native-picker/picker | Expo UI Picker |
react-native-pager-view | Expo UI pager components |
@react-native-segmented-control/segmented-control | Expo UI segmented control |
Native State and Worklet Callbacks
SDK 56 Expo UI integrates with react-native-worklets and native state primitives. Use this path for
text input, animation, and high-frequency UI where JS round trips are visible.
import { Host, TextField, useNativeState } from '@expo/ui/swift-ui';
export function SearchField() {
const value = useNativeState('');
return (
<Host matchContents>
<TextField text={value} placeholder="Search" />
</Host>
);
}AI Integration
Expo provides official Expo Skills such as expo-ui-swift-ui, expo-ui-jetpack-compose, and
building-native-ui.
npx skills add expo/skillsGenerated code still needs npx expo-doctor, TypeScript, and real device builds. Native layout,
touch targets, system fonts, and Dynamic Color cannot be fully validated in a web preview.
Caveats
- Expo UI components live in a native tree that differs from React Native View, so avoid
measure()andonLayoutdependencies. - Universal web APIs can still change; keep web critical flows under regression tests.
- Native modifiers do not mean the same thing on each platform. Keep tokens shared but verify implementation per platform.
- Check accessibility labels, keyboard behavior, and screen reader order for every replacement.