SwiftUI
SwiftUI components for building native iOS interfaces with @expo/ui.
The SwiftUI components in @expo/ui/swift-ui allow you to build fully native iOS interfaces using SwiftUI from React Native.
Installation
bun expo install @expo/uiIf you are installing this in an existing React Native app, make sure to install expo in your project.
Usage
Using a component from @expo/ui/swift-ui requires wrapping it in a Host component. The Host is a container for SwiftUI views.
For more information, see the following resources:
Expo UI guide for Swift UI — Learn about the basics of @expo/ui/swift-ui — @expo/ui/swift-ui
Extending with SwiftUI — Create custom SwiftUI components and modifiers that integrate with Expo UI.
Expo UI iOS Liquid Glass Tutorial — Learn how to build real SwiftUI views in your React Native app with the new Expo UI.
Available components

































| Component | Description |
|---|---|
AccessoryWidgetBackground | Adaptive background view that provides a standard appearance based on the widget's environment. |
Alert | Alert component for presenting native iOS alert dialogs. |
BottomSheet | BottomSheet component that presents content from the bottom of the screen. |
Button | Button component for displaying native buttons. |
ColorPicker | ColorPicker component for selecting colors. |
ConfirmationDialog | ConfirmationDialog component for presenting confirmation prompts. |
ContextMenu | ContextMenu component for displaying context menus. |
ControlGroup | ControlGroup component for grouping interactive controls. |
DatePicker | DatePicker component for selecting dates and times. |
DisclosureGroup | DisclosureGroup component for displaying expandable content. |
Divider | Divider component for creating visual separators. |
Form | Form component for collecting user input in a structured layout. |
Gauge | Gauge component for displaying progress with visual indicators. |
Group | Group component for grouping views without affecting layout. |
Host | Host component that enables SwiftUI components in React Native. |
HStack | HStack component for horizontal layouts. |
Image | Image component for displaying SF Symbols. |
Label | Label component for displaying text with an icon. |
LazyHStack | LazyHStack component for lazy horizontal layouts. |
LazyVStack | LazyVStack component for lazy vertical layouts. |
Link | Link component for displaying clickable links. |
List | List component for displaying scrollable lists of items. |
Menu | Menu component for displaying dropdown menus. |
Modifiers | View modifiers for customizing component appearance and behavior. |
Namespace | A Namespace component that allows you create Namespaces in SwiftUI |
Overlay | Overlay component for layering content on top of another view. |
Picker | Picker component for selecting options from a list. |
Popover | Popover component for displaying content in a floating overlay. |
ProgressView | ProgressView component for displaying progress indicators. |
RNHostView | A component that enables React Native views inside SwiftUI. |
ScrollView | ScrollView component for scrollable content. |
Section | Section component for grouping content within lists and forms. |
SecureField | SecureField component for password input. |
Slider | Slider component for selecting values from a range. |
Spacer | Spacer component for flexible spacing. |
SwipeActions | SwipeActions component for adding leading and trailing swipe actions to row content. |
TabView | TabView component for paged or tabbed content. |
Text | Text component for displaying styled text with support for nested texts. |
TextField | TextField component for text input. |
Toggle | Toggle component for displaying native toggles. |
useNativeState | A React hook that creates observable state shared between JavaScript and native SwiftUI views. |
VStack | VStack component for vertical layouts. |
ZStack | ZStack component for overlapping layouts. |
Build a SwiftUI screen
Available in SDK 54 and later.
Expo UI brings SwiftUI to React Native. You can use modern SwiftUI primitives to build your apps.
This guide covers the basics of using Expo UI to integrate SwiftUI into your Expo apps.
Expo UI iOS Liquid Glass Tutorial — Learn how to build real SwiftUI views in your React Native app with the new Expo UI.
Features
- SwiftUI primitives: Expo UI is not another UI library. It brings SwiftUI primitives to Expo.
- 1-to-1 mapping: The components in Expo UI have a 1-to-1 mapping to SwiftUI views. You can easily explore available views in the SwiftUI ecosystem, such as Explore SwiftUI or the Libraried app, and find the corresponding Expo UI component.
- Full-app support: Expo UI is designed to be used throughout the entire app. You can write your app entirely in Expo UI, while maintaining flexibility at the same time. The integration works at the component level. You can also mix React Native components, Expo UI components, DOM components, or custom 2D components using
react-native-skia.
Installation
You'll need to install the @expo/ui package in your Expo project. Run the following command to install it:
npx expo install @expo/uiExpo Skills for AI agents
If you use an AI agent, install Expo Skills to teach it how to build native-feeling screens:
expo-ui — Build native UI with the @expo/ui package: real SwiftUI on iOS and Jetpack Compose on Android.
expo-native-ui — Build beautiful, native-feeling Expo screens.
Usage
Expo UI has several SwiftUI components available.
- You can use them in your app by importing them from
@expo/ui/swift-ui. - However, to cross the boundary from React Native (UIKit) to SwiftUI, you need to use the 1 component.
- The 2 is the container for SwiftUI views.
- You can think of it like 3 in the DOM or 4 in 5.
- Under the hood, it uses 6 to render SwiftUI views in UIKit.
Basic usage with Host
Code
import { CircularProgress, Host } from '@expo/ui/swift-ui';
import { View, Text } from 'react-native';
export default function LoadingView() {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Host matchContents>
<CircularProgress />
</Host>
<Text>Loading...</Text>
</View>
);
}Preview
Using HStack and VStack
You can also use the HStack and VStack components to build the entire layout in SwiftUI.
Code
import { CircularProgress, Host, HStack, LinearProgress, VStack } from '@expo/ui/swift-ui';
export default function LoadingView() {
return (
<Host style={{ flex: 1, margin: 32 }}>
<VStack spacing={32}>
<HStack spacing={32}>
<CircularProgress />
<CircularProgress color="orange" />
</HStack>
<LinearProgress progress={0.5} />
<LinearProgress color="orange" progress={0.7} />
</VStack>
</Host>
);
}Preview
Modifiers
SwiftUI modifier is a powerful way to customize the appearance and behavior of SwiftUI components.
- Expo UI also provides modifiers for SwiftUI components.
- You can import modifiers from
@expo/ui/swift-ui/modifiersand pass them as an array to themodifiersprop. - In the following example, the 2 and
glassEffectmodifier are combined to create Liquid Glass text.
Code
Note:
glassEffectmodifier requires Xcode 26+ and iOS 26+.
import { Host, Text } from '@expo/ui/swift-ui';
import { glassEffect, padding } from '@expo/ui/swift-ui/modifiers';
import { MeshGradientView } from 'expo-mesh-gradient';
import { View } from 'react-native';
export default function Page() {
return (
<View style={{ flex: 1 }}>
<MeshGradientView
style={{ flex: 1 }}
columns={3}
rows={3}
colors={['red', 'purple', 'indigo', 'orange', 'white', 'blue', 'yellow', 'green', 'cyan']}
points={[
[0.0, 0.0],
[0.5, 0.0],
[1.0, 0.0],
[0.0, 0.5],
[0.5, 0.5],
[1.0, 0.5],
[0.0, 1.0],
[0.5, 1.0],
[1.0, 1.0],
]}
/>
<Host style={{ position: 'absolute', top: 0, right: 0, left: 0, bottom: 0 }}>
<Text
size={32}
modifiers={[
padding({
all: 16,
}),
glassEffect({
glass: {
variant: 'clear',
},
}),
]}>
Glass effect text
</Text>
</Host>
</View>
);
}Preview
iOS Settings app example
Combining the Expo UI components and modifiers, you can build a UI like iOS Settings app.
Code
import {
Button,
Form,
Host,
HStack,
Image,
Section,
Spacer,
Toggle,
Text,
} from '@expo/ui/swift-ui';
import { background, buttonStyle, foregroundStyle, clipShape, frame } from '@expo/ui/swift-ui/modifiers';
import { Link } from 'expo-router';
import { useState } from 'react';
export default function SettingsView() {
const [isAirplaneMode, setIsAirplaneMode] = useState(true);
return (
<Host style={{ flex: 1 }}>
<Form>
<Section>
<HStack spacing={8}>
<Image
systemName="airplane"
color="white"
size={18}
modifiers={[
frame({ width: 28, height: 28 }),
background('#ffa500'),
clipShape('roundedRectangle'),
]}
/>
<Text>Airplane Mode</Text>
<Spacer />
<Toggle isOn={isAirplaneMode} onIsOnChange={setIsAirplaneMode} />
</HStack>
<Link href="/wifi" asChild>
{/* Use buttonStyle('plain') to prevent default blue button styling */}
<Button modifiers={[buttonStyle('plain')]}>
<HStack spacing={8}>
<Image
systemName="wifi"
color="white"
size={18}
modifiers={[
frame({ width: 28, height: 28 }),
background('#007aff'),
clipShape('roundedRectangle'),
]}
/>
{/* When Text is wrapped in a Link, the color needs to be specified explicitly */}
<Text modifiers={[foregroundStyle({type: 'color', color: 'black'})]}>Wi-Fi</Text>
<Spacer />
<Image systemName="chevron.right" size={14} color="secondary" />
</HStack>
</Button>
</Link>
</Section>
</Form>
</Host>
);
}Preview
Secondary text styling
Use foregroundStyle to apply a hierarchical style, which will make text appear lighter and more subtle.
Code
import { Button, Form, Host, HStack, Image, List, Section, Spacer, Text } from '@expo/ui/swift-ui';
import { buttonStyle, font, foregroundStyle, padding } from '@expo/ui/swift-ui/modifiers';
export default function SecondaryTextExample() {
return (
<Host style={{ flex: 1 }}>
<Form>
<Section>
<List>
<Button onPress={() => console.log('Navigate')} modifiers={[buttonStyle('plain')]}>
<HStack>
<Text>Night Shift</Text>
<Spacer />
<Text
modifiers={[
foregroundStyle({type: 'hierarchical', style: 'secondary'}),
padding({ trailing: 8 }),
]}>
22:00 to 07:00
</Text>
<Image systemName="chevron.right" size={14} color="#C7C7CC" />
</HStack>
</Button>
</List>
<List>
<Text modifiers={[foregroundStyle({type: 'hierarchical', style: 'secondary'}), font({ size: 14 })]}>
Save up to 280.7 MB. This will permanently delete all photos and videos kept in the
"Recently Deleted" album.
</Text>
</List>
</Section>
</Form>
</Host>
);
}Preview
Slider with icons
A common pattern for brightness or volume controls is to flank a Slider with icons.
Code
import { useState } from 'react';
import {
Form,
Host,
HStack,
Image,
List,
Section,
Slider,
Spacer,
Text,
Toggle,
} from '@expo/ui/swift-ui';
import { padding } from '@expo/ui/swift-ui/modifiers';
export default function SliderWithIconsExample() {
const [brightness, setBrightness] = useState(0.5);
const [trueToneEnabled, setTrueToneEnabled] = useState(true);
return (
<Host style={{ flex: 1 }}>
<Form>
<Section
header={<Text>Brightness</Text>}
footer={
<Text>
Automatically adapt iPhone display based on ambient lighting
conditions to make colors appear consistent in different
environments.
</Text>
}
>
<List>
<HStack modifiers={[padding({ vertical: 6 })]}>
<Image systemName="sun.min.fill" size={22} color="#8E8E93" />
<Spacer />
<Slider value={brightness} onValueChange={setBrightness} />
<Spacer />
<Image systemName="sun.max.fill" size={22} color="#8E8E93" />
</HStack>
<Toggle
label="True Tone"
isOn={trueToneEnabled}
onIsOnChange={setTrueToneEnabled}
/>
</List>
</Section>
</Form>
</Host>
);
}Preview
Multi-line list items
Use VStack with alignment="leading" for list items with title and subtitle.
Code
import {
Button,
Form,
Host,
HStack,
Image,
List,
Section,
Spacer,
Text,
VStack,
} from '@expo/ui/swift-ui';
import { buttonStyle, font, foregroundStyle, padding } from '@expo/ui/swift-ui/modifiers';
export default function MultiLineListItemExample() {
return (
<Host style={{ flex: 1 }}>
<Form>
<Section>
<List>
<HStack>
<Image
systemName="safari"
size={22}
modifiers={[padding({ trailing: 6 })]}
/>
<Spacer />
<Button
onPress={() => console.log('Navigate')}
modifiers={[buttonStyle('plain'), padding({ vertical: 6 })]}
>
<VStack spacing={4} alignment="leading">
<Text>Chrome</Text>
<Text modifiers={[foregroundStyle({type: 'hierarchical', style: 'secondary'}), font({ size: 14 })]}>
Last used: Today
</Text>
</VStack>
<Spacer />
<Text
modifiers={[
foregroundStyle({type: 'hierarchical', style: 'secondary'}),
font({ size: 16 }),
]}
>
1.57 GB
</Text>
<Image systemName="chevron.right" size={14} color="#C7C7CC" />
</Button>
</HStack>
</List>
</Section>
</Form>
</Host>
);
}Preview
Common questions
Can I use flexbox or other styles in SwiftUI components?
Flexbox styles can be applied to the Host component itself. Once you're inside the SwiftUI context, however, Yoga is not available — layouts should be defined using <HStack> and <VStack> instead.
What's the Host component?
Host is the container for SwiftUI views.
- You can think of it like 1 in the DOM or 2 in 3.
- Under the hood, it uses 4 to render SwiftUI views in UIKit.
How is Expo UI different from libraries like react-native-paper or react-native-elements?
Expo UI is not "yet another" UI library and not an opinionated design kit. Instead, it's a primitives library. It exposes native SwiftUI and Jetpack Compose components directly to JavaScript, rather than re-implementing or simulating UI in JavaScript.
Can I use React Native components inside SwiftUI components?
Yes, you can place React Native components as JSX children of Expo UI components.
- Expo UI automatically creates a 0 wrapper for you.
- However, keep in mind that the SwiftUI layout system works differently from UIKit and has some limitations.
- According to Apple's documentation:
SwiftUI fully controls the layout of the UIKit view's
center,bounds,frame, andtransformproperties. Don't directly set these layout-related properties on the view managed by aUIViewRepresentableinstance from your own code because that conflicts with SwiftUI and results in undefined behavior.
Also note that once you render React Native components, you're leaving the SwiftUI context. If you want to add Expo UI components again, you'll need to reintroduce a Host wrapper.
We recommend keeping SwiftUI layouts self-contained. Interop is possible, but it works best when boundaries are clearly defined.
I'm a SwiftUI developer. Why should I learn Expo UI?
Because React's promise of "learn once, write anywhere", it now extends to SwiftUI and Jetpack Compose.
- With Expo UI, you can apply your SwiftUI knowledge to build apps that run in the React Native ecosystem, extend to the Web through DOM components, and even integrate 2D and 3D rendering.
- The system is flexible enough that different parts of your app can use different approaches — giving you seamless integration at the component level.
Additional resources
Expo UI reference — For information on API components, methods, and more, see the Expo UI reference.
Expo UI example — Our latest Expo UI examples.
Hot Chocolate app example — An example app replicating the YVR Hot Chocolate Fest app with Expo UI.
Expo UI multiplatform demo — A project demonstrating the production-ready Expo UI package available in SDK 56 and later.
- The project works on both TV (Android TV, Apple TV) and mobile (Android, iOS).
- Demo screens include most of the available components for both Jetpack Compose and Swift UI.
Add your own SwiftUI views
This guide explains how to create custom SwiftUI components and modifiers that integrate seamlessly with Expo UI.
Prerequisites
@expo/ui installed
Install @expo/ui in your project. See Building SwiftUI apps with Expo UI for more information.
npx expo install @expo/uiA development build
Expo UI is not available in Expo Go. Create a development build of your app.
Familiarity with Expo Modules API and SwiftUI
Basic familiarity with Expo Modules API and SwiftUI is recommended.
Creating a custom component
Project setup
Create a local Expo module in your project:
npx create-expo-module@latest --local my-uiAdd ExpoUI as a dependency in your module's podspec file:
Pod::Spec.new do |s|
s.name = 'MyUi'
s.version = '1.0.0'
s.summary = 'Custom UI components extending Expo UI'
# ... other config
# Add ExpoUI dependency
s.dependency 'ExpoUI'
# ... other config
endCreating a SwiftUI view
Create your SwiftUI view with two parts:
- Props class: Extends
UIBaseViewPropsfromExpoUIto get automatic support for themodifiersprop - View struct: Conforms to
ExpoSwiftUI.Viewprotocol, which requires an@ObservedObjectprops property and abody
import SwiftUI
import ExpoModulesCore
import ExpoUI
final class MyCustomViewProps: UIBaseViewProps {
@Field var title: String = ""
}
struct MyCustomView: ExpoSwiftUI.View {
@ObservedObject public var props: MyCustomViewProps
var body: some View {
VStack {
Text(props.title)
.font(.headline)
Children() // Renders React children
}
}
}Register the view in your module using ExpoUIView. This wraps your SwiftUI view with modifier support and makes it available to JavaScript:
import ExpoModulesCore
import ExpoUI
public class MyUiModule: Module {
public func definition() -> ModuleDefinition {
Name("MyUi")
ExpoUIView(MyCustomView.self)
}
}Create a wrapper component that connects modifiers with event handling. The createViewModifierEventListener utility enables event-based modifiers like onTapGesture and onAppear to work with your custom view:
import { requireNativeView } from 'expo';
import { type CommonViewModifierProps } from '@expo/ui/swift-ui';
import { createViewModifierEventListener } from '@expo/ui/swift-ui/modifiers';
export interface MyCustomViewProps extends CommonViewModifierProps {
title: string;
children?: React.ReactNode;
}
const NativeMyCustomView = requireNativeView<MyCustomViewProps>('MyUi', 'MyCustomView');
export function MyCustomView({ modifiers, ...restProps }: MyCustomViewProps) {
return (
<NativeMyCustomView
modifiers={modifiers}
{...(modifiers ? createViewModifierEventListener(modifiers) : undefined)}
{...restProps}
/>
);
}Using your custom component
Your custom component now works with all ExpoUI built-in modifiers:
import { Host, Text } from '@expo/ui/swift-ui';
import { padding, cornerRadius, background } from '@expo/ui/swift-ui/modifiers';
import { MyCustomView } from './modules/my-ui';
export default function App() {
return (
<Host style={{ flex: 1 }}>
<MyCustomView
title="Hello World"
modifiers={[padding({ all: 16 }), cornerRadius(12), background('#f0f0f0')]}>
<Text>Child content</Text>
</MyCustomView>
</Host>
);
}Creating custom modifiers
You can also create custom modifiers that work with any Expo UI component.
Modifiers are SwiftUI's way to configure views for styling, layout, behavior, and more. Learn more in Apple's ViewModifier documentation.
Native modifier implementation
Create a modifier struct that conforms to ViewModifier and Record:
import SwiftUI
import ExpoModulesCore
import ExpoUI
struct CustomBorderModifier: ViewModifier, Record {
@Field var color: Color = .red
@Field var width: CGFloat = 2
@Field var cornerRadius: CGFloat = 0
func body(content: Content) -> some View {
content
.overlay(
RoundedRectangle(cornerRadius: cornerRadius)
.stroke(color, lineWidth: width)
)
}
}Register your modifier with ViewModifierRegistry in your module definition. Use OnCreate to register and OnDestroy to unregister to avoid race conditions with the SwiftUI render thread:
import ExpoModulesCore
import ExpoUI
public class MyUiModule: Module {
public func definition() -> ModuleDefinition {
Name("MyUi")
OnCreate {
ViewModifierRegistry.register("customBorder") { params, appContext, _ in
return try CustomBorderModifier(from: params, appContext: appContext)
}
}
OnDestroy {
ViewModifierRegistry.unregister("customBorder")
}
ExpoUIView(MyCustomView.self)
}
}JavaScript modifier function
Create a TypeScript function that generates the modifier config:
import { createModifier } from '@expo/ui/swift-ui/modifiers';
export const customBorder = (params: { color?: string; width?: number; cornerRadius?: number }) =>
createModifier('customBorder', params);Export the modifier from your module:
export { MyCustomView, type MyCustomViewProps } from './src/MyCustomView';
export { customBorder } from './src/modifiers';Using custom modifiers
Your custom modifier works with any ExpoUI component:
import { Host, Text, VStack } from '@expo/ui/swift-ui';
import { padding } from '@expo/ui/swift-ui/modifiers';
import { customBorder } from './modules/my-ui';
export default function App() {
return (
<Host style={{ flex: 1 }}>
<VStack
modifiers={[
padding({ all: 20 }),
customBorder({ color: '#FF6B35', width: 3, cornerRadius: 8 }),
]}>
<Text>This has a custom border!</Text>
</VStack>
</Host>
);
}Next steps
Congratulations! You've learned how to extend Expo UI with custom SwiftUI components and modifiers. Your custom components now integrate seamlessly with the built-in modifier system.
Here are some ideas for what to build next:
- Use the built-in SwiftUI components that come with Expo UI
- Build custom modifiers for app-specific styling patterns
- Wrap third-party SwiftUI libraries for use in React Native
- Share your components as an npm package for others to use
Sources
The original Expo docs for this page:
- SwiftUI (
source-of-truth/versions/v57.0.0/sdk/ui/swift-ui.md) - Building SwiftUI apps with Expo UI (
source-of-truth/guides/expo-ui-swift-ui.md) - Extending with SwiftUI (
source-of-truth/guides/expo-ui-swift-ui/extending.md)
































