React Native Rocks

Expo UI: Truly Native iOS and Android Components In React Native

David Nemes
David Nemes

Expo UI lets you use truly native SwiftUI and Jetpack Compose components directly inside your Expo apps. Instead of re‑implementing native UI in JavaScript, Expo UI exposes real native components to JavaScript, giving you native performance, native behavior, and native capabilities without giving up React.

Key Takeaways

  • Native SwiftUI and Jetpack Compose components in JavaScript
  • Platform-specific code is required if you want to support both iOS and Android
  • Native components need to be wrapped in a Host component
  • Inside Host component layout is handled with HStack and VStack
  • SwiftUI components can be styled using @expo/ui/swift-ui/modifiers
  • Has TypeScript support
  • Not supported in Expo Go (Jetpack Compose requires development builds)

React Native is excellent for building cross-platform apps, but there are times when you need direct access to native platform features that aren't yet bridged or when you want to use the latest SwiftUI and Jetpack Compose components directly. You also get native performance and behavior out of the box, and you can mix React Native, Expo UI, and custom components seamlessly.

An important consideration is that if you want to support both iOS and Android, you'll need to write platform-specific files (.ios.tsx and .android.tsx) using the appropriate Expo UI package for each platform.

Swift UI Components

Expo UI for SwiftUI covers the most common SwiftUI components. New components are continually being added, and you can check the up-to-date list in the official documentation.

Usage
Every SwiftUI component must be wrapped in a Host component. Then, you simply import the desired SwiftUI component.

button.ios.tsx
import { Host, Button } from '@expo/ui/swift-ui';

export default function SaveButton() {
  return (
    <Host style={{ flex: 1 }}>
      <Button variant="default">
        Save changes
      </Button>
    </Host>
  );
}

Layout with HStack and VStack

Inside SwiftUI contexts, you don't use Flexbox. Instead, use HStack (horizontal) and VStack (vertical) for layout:

button.ios.tsx
import { Host, Button, VStack } from '@expo/ui/swift-ui';

export default function SaveButton() {
  return (
    <Host style={{ flex: 1 }}>
      <VStack>
        <Button variant="default">
          Save changes
        </Button>
        <Button variant="default">
          Delete changes
        </Button>
      </VStack>
    </Host>
  );
}

Modifiers

SwiftUI modifiers are a powerful way to customize the appearance and behavior of SwiftUI components. Expo UI exposes these modifiers as well, allowing you to style SwiftUI components by importing them from @expo/ui/swift-ui/modifiers and passing them as an array to the modifiers prop.

button.ios.tsx
import { Host, Button } from '@expo/ui/swift-ui';
import { padding } from '@expo/ui/swift-ui/modifiers';

export default function SaveButton() {
  return (
    <Host style={{ flex: 1 }}>
      <Button
        variant="default"
        modifiers={[
          padding({
            all: 16,
          }),
        ]}
      >
        Save changes
      </Button>
    </Host>
  );
}

Jetpack Compose Components (Alpha)

Android is supported via Jetpack Compose. New components are continually being added, and you can check the up-to-date list in the official documentation.

Usage
Every Jetpack component must be wrapped in a Host component. Then, you simply import the desired Jetpack component.

button.android.tsx
import { Button, Host } from '@expo/ui/jetpack-compose';

export default function SaveButton() {
  return (
    <Host style={{ flex: 1 }}>
      <Button variant="default">
        Save changes
      </Button>
    </Host>
  );
}