Expo UISwiftUIJetpack ComposeReact Native Components

Expo UI Guide: Use SwiftUI and Jetpack Compose Components in React Native

Learn @expo/ui — render real SwiftUI and Jetpack Compose components from React. Covers the universal API, Host, Switch, Button, FieldGroup settings screens, platform-specific imports, modifiers, and when to use Expo UI vs regular React Native views.

DeveloperMill4 min read

For years, React Native apps re-created native controls in JavaScript: switches, sliders, pickers, and settings lists that were almost native. Expo UI (@expo/ui) takes a different approach. It renders the real SwiftUI views on iOS and real Jetpack Compose views on Android from React components.

The SwiftUI and Compose APIs became stable in SDK 56, and SDK 58 added recycling lists and native navigation components. If you're building settings screens, forms, or anything that should feel exactly like the OS, Expo UI is now the first thing to reach for.

Install

npx expo install @expo/ui

Expo UI is included in Expo Go, so you can try it immediately. It works the same in development and production builds.

Three ways to import

  • @expo/ui is the universal API. One component maps to SwiftUI on iOS and Compose on Android. Start here.
  • @expo/ui/swift-ui gives you SwiftUI-named components (Toggle, Form, List) when you want iOS-specific behavior.
  • @expo/ui/jetpack-compose gives you Compose-named components (LazyColumn, and so on) for Android-specific UI.

You can mix universal and platform-specific components inside the same Host.

Your first component: a native Switch

Every Expo UI component must live inside a Host, which bridges React Native's view tree to the native UI tree:

import { useState } from "react"
import { Host, Switch } from "@expo/ui"

export function NotificationsToggle() {
  const [enabled, setEnabled] = useState(true)

  return (
    <Host matchContents={{ vertical: true }} style={{ width: "100%" }}>
      <Switch
        label="Enable notifications"
        value={enabled}
        onValueChange={setEnabled}
      />
    </Host>
  )
}

matchContents tells the Host to size itself to the native content. Flexbox styles (style) apply to the Host, not to the SwiftUI or Compose view inside it.

Buttons with native variants

The universal Button supports three variants that map to each platform's own styles:

import { Button, Host } from "@expo/ui"

<Host matchContents>
  <Button variant="filled" label="Continue" onPress={onContinue} />
</Host>
  • filled (the default) has a solid background.
  • outlined has a transparent background with a border.
  • text shows the label only.

Pass children instead of label when you need custom content inside the button.

Build a native settings screen

The most popular use for Expo UI is the settings screen. FieldGroup gives you grouped sections that look like iOS Settings on iPhone and follow Material on Android:

import { useState } from "react"
import { FieldGroup, Host, Slider, Switch, Text } from "@expo/ui"

export default function SettingsScreen() {
  const [notifications, setNotifications] = useState(true)
  const [haptics, setHaptics] = useState(true)
  const [volume, setVolume] = useState(0.6)

  return (
    <Host style={{ flex: 1 }}>
      <FieldGroup>
        <FieldGroup.Section title="Notifications">
          <Switch label="Push notifications" value={notifications} onValueChange={setNotifications} />
          <Switch label="Haptic feedback" value={haptics} onValueChange={setHaptics} />
        </FieldGroup.Section>

        <FieldGroup.Section title="Sound">
          <Text>Volume</Text>
          <Slider value={volume} onValueChange={setVolume} />
        </FieldGroup.Section>
      </FieldGroup>
    </Host>
  )
}

That's a fully native settings screen. Dynamic Type, dark mode, VoiceOver and TalkBack, and platform spacing come for free.

Universal components you can use today

The universal layer covers the building blocks most screens need:

  • Layout: Host, Row, Column, ScrollView, Spacer
  • Forms: FieldGroup, FieldGroup.Section, FieldGroup.SectionFooter
  • Controls: Switch, Slider, Button
  • Content: Text

The platform-specific packages go much further: date pickers, menus, sheets, lists, and (new in SDK 58) NavigationStack, Toolbar, and NavigationLink on iOS.

Modifiers: the SwiftUI and Compose way to style

Platform components accept a modifiers prop. Modifier names follow Apple's and Google's docs, so the SwiftUI and Compose references apply directly. This is also how you access platform-only effects such as SwiftUI's glassEffect() for Liquid Glass. For a no-SwiftUI way to add glass to normal views, see our Liquid Glass and glassmorphism guide.

Recycling lists in SDK 58

Before SDK 58, Expo UI lists rendered every row. Now List, LazyVStack, LazyHStack, LazyColumn, and LazyRow virtualize and recycle rows, so you can use them for long, data-driven lists. For very large or highly custom feeds, FlashList v2 is still the more flexible choice.

Expo UI vs regular React Native components

Use Expo UI when:

  • The screen should feel exactly like the OS (settings, forms, preferences, pickers).
  • Accessibility and platform conventions matter more than custom branding.
  • You want new OS design changes, like Liquid Glass, without rewriting anything.

Use React Native views (and NativeWind) when:

  • You need a custom, branded look that's identical on both platforms.
  • You're building bespoke interactions such as gesture-driven sheets, animated pickers, or confetti.
  • You need full web parity today (Expo UI web is still experimental).

Most production apps use both: Expo UI for system-style screens, and custom components for the brand moments.

Custom components that pair with Expo UI

Our free, MIT-licensed components cover the custom side: a gesture-driven bottom sheet, a curved arc wheel picker, and an animated bottom navigation bar. Browse everything in the component library, and see what else shipped in Expo SDK 58.

Frequently asked questions

What is Expo UI?

Expo UI (@expo/ui) is an Expo package that renders real SwiftUI views on iOS and Jetpack Compose views on Android from React components. Instead of re-creating native controls in JavaScript, you get the actual platform components — switches, sliders, pickers, forms, lists — with their native look, behavior, and accessibility.

Is Expo UI stable?

Yes. The SwiftUI and Jetpack Compose APIs were declared stable in Expo SDK 56 (May 2026). SDK 56 also added a universal `@expo/ui` import that maps to SwiftUI on iOS and Compose on Android. The web implementation is still experimental.

Do I need to wrap Expo UI components in Host?

Yes. Every Expo UI component must be rendered inside a `Host`, which bridges the React Native view tree to the SwiftUI or Compose tree. Flexbox styles apply to the Host itself, and `matchContents` makes the Host size itself to its content.

Can I mix Expo UI with regular React Native components?

Yes. A Host is just a view in your React Native layout, so you can place Expo UI sections next to normal View, Text, and NativeWind-styled components. Many apps use Expo UI for settings screens and form controls and regular React Native views for custom-branded UI.

Build it faster with a template

Production-ready Expo React Native templates with the patterns from this post already wired up.

Browse templates