React Native
Building SwiftUI apps with Expo UI
Want iOS 26 Liquid Glass in a React Native app without writing Swift? How Expo UI puts real SwiftUI views inside Expo, with a working example.
Want Liquid Glass in your app but don't know how to write SwiftUI? This post is for you.
The official resources, if you'd rather go straight to the source:
- Expo docs: Building SwiftUI apps with Expo UI (opens in a new tab)
- YouTube: the Expo UI walkthrough (opens in a new tab)
Before we start
Hi everyone. This is a quick write-up of something I only started exploring recently, so if anything here looks off, let's talk it through.
Straight to the point, then. Look at the design below. Do you see a problem?

With iOS 26, Apple shipped a new UI with modern materials and interactions: Liquid Glass. It isn't just a "prettier blur". It's a shift in design language and UI primitives, where animation, material, depth and interaction are all tied to the native rendering engine.
More importantly, Apple is moving the UI and UX of its whole ecosystem to this new design language. That tells us it's a long-term strategy, not a one-off visual refresh.
So what's the problem?
The platform's UI layer is changing fast, and we developers are left running to keep up.
If you build apps with React Native or Expo, you run into a few challenges:
If the platform is investing in SwiftUI primitives, how does our React Native app get to those primitives?
- The new effects only exist in SwiftUI (iOS) or Jetpack Compose (Android).
- There aren't many JS libraries that reproduce the real native behaviour, and the ones that exist each cover a single feature.
- Most current solutions just imitate the look with JS and CSS-like styling.
- Rewriting everything in Swift? Not realistic for teams that already have a large React Native codebase.
This is where @expo/ui comes in: it lets us use everything Expo can do, without building custom native modules or pulling in third-party libraries.
What is Expo UI, and why use it?
I won't cover what React Native or Expo are here; I'm sure you know them well by now.
And of course @expo/ui wasn't made only for Liquid Glass. It gives developers access to the platform's native primitives as soon as a new OS version ships. Liquid Glass is just the easiest case to see.
Instead of re-implementing UI in JavaScript, Expo UI exposes SwiftUI (iOS) and Jetpack Compose (Android) directly inside React Native. That means you can use the latest native UI primitives, including materials and interactions, without leaving the Expo ecosystem.
Expo UI was introduced in Expo SDK 53, but it's with SDK 54 that @expo/ui supports almost the full set of SwiftUI (some views and modifiers are still on the roadmap), rather than a few small components like Switch, Button and Slider as in the previous version.
How it works
SwiftUI primitives, not a new UI library
Expo UI isn't a new UI library. It brings SwiftUI's primitives into Expo and React Native, so you get native iOS UI and better performance.
That makes it different from earlier libraries like react-native-elements: Expo UI maps React components 1-to-1 to native SwiftUI views instead of simulating them in JS.
1-to-1 mapping
Expo UI components correspond almost directly to SwiftUI views. If you know SwiftUI, you'll pick Expo UI up quickly, and you can read SwiftUI documentation and apply it the same way. Two references I use:
- Explore SwiftUI (opens in a new tab), a visual catalogue of SwiftUI views
- Libraried (opens in a new tab), an app that shows UI components running on device
Full-app support
Expo UI is designed to be used throughout an app, not just for a few isolated components. You can build a whole app with it and still stay flexible:
- Mix React Native components (opens in a new tab), Expo UI components (opens in a new tab) and DOM components (opens in a new tab) in the same screen.
- Add custom 2D graphics or animation with
react-native-skia(opens in a new tab). - Use SwiftUI's icon features, such as SF Symbols, as well.
Here a native SwiftUI date picker sits inside an ordinary React Native card:
<View className="bg-background mb-6 flex flex-row rounded-xl px-6 py-4">
<View className="flex-1 gap-3">
<Text className="text-sectionTitle text-sm font-lexendSemiBold">
{t("createDebtBorrowDateLabel")}
</Text>
{/* SwiftUI component */}
<Host matchContents>
<DateTimePicker
variant="automatic"
color="#FF7A45"
onDateSelected={(date) => console.log(date)}
/>
</Host>
</View>
</View>
Expo UI by example
Install
npx expo install @expo/ui
Layout structure
<Host>: Expo UI uses <Host> as the root container for SwiftUI. Think of it as the bridge entry point that lets SwiftUI run inside Expo, so every SwiftUI component has to be wrapped in one.
Inside it, <VStack> and <HStack> lay views out in a column or a row, as they do in a SwiftUI hierarchy.
Putting React Native components inside SwiftUI
SwiftUI components only work under a <Host>. React Native components belong to a different UI tree, so putting one inside a <Host> usually doesn't work properly or fails to render.
Rendering a React Native component means you've left the SwiftUI context. If you want to use Expo UI components again after that, wrap them in a new <Host> to bring the SwiftUI context back.
Doesn't render:
<Host>
<VStack>
<Slider />
</VStack>
{/* React Native component */}
<Text>React Native footer</Text>
</Host>
Works:
<View>
<Host>
<VStack>
<Slider />
</VStack>
</Host>
<Text>React Native footer</Text>
</View>
An example I built: an adaptive glass button
This button uses the native SwiftUI glass button where Liquid Glass is available, and falls back to a blurred React Native button everywhere else (older iOS, Android, Expo Go):
import { Host, Button, Image, Label, Text } from "@expo/ui/swift-ui";
import { BlurView } from "expo-blur";
import Constants from "expo-constants";
import { isLiquidGlassAvailable } from "expo-glass-effect";
import { useColorScheme } from "nativewind";
import React from "react";
import { Pressable, View } from "react-native";
import { type SFSymbol } from "sf-symbols-typescript";
// Icons instruction
// https://www.figma.com/design/ffyL6r98YI4HFkXmu9TRRO/SF-Symbols-%7C-Text-objects-for-Figma--Community-?node-id=2004-3&t=ui1HTrkThWzLB9ab-0
export function AdaptiveGlassButton({
children,
onPress,
disabled,
color,
icon,
title,
}: {
children?: React.ReactNode;
onPress?: () => void;
disabled?: boolean;
color?: string;
icon?: SFSymbol;
title?: string;
}) {
const useGlass = isLiquidGlassAvailable();
const isExpoGo = Constants.appOwnership === "expo";
const theme = useColorScheme().colorScheme;
return useGlass && !isExpoGo ? (
<Pressable onPress={onPress} disabled={disabled} style={{ opacity: disabled ? 0.5 : 1 }}>
<Host matchContents>
{title ? (
<Button variant="glass" controlSize="regular">
<Label title={title} />
</Button>
) : (
<Button variant="glass" controlSize="regular">
<Image
systemName={icon ? icon : "arrow.backward"}
color={color}
frame={{ width: 20, height: 30, alignment: "center" }}
/>
</Button>
)}
</Host>
</Pressable>
) : (
<Pressable
onPress={onPress}
disabled={disabled}
className="w-11 h-11 overflow-hidden rounded-full"
style={{ opacity: disabled ? 0.5 : 1 }}
>
<BlurView
intensity={10}
tint={theme === "light" ? "systemThinMaterialDark" : "extraLight"}
className="absolute inset-0"
>
<View className="flex-1 items-center justify-center">{children}</View>
</BlurView>
</Pressable>
);
}
And here's the result
Expo UI doesn't stop at the look. It also uses native behaviour: touch, press and drag feedback come from the platform itself. On the latest iOS, with Liquid Glass, buttons and interactions get the real native effect instead of a JS imitation.
These effects aren't always available, though. Android, or older iOS versions, may not fully support them. So when you build a cross-platform app, you usually need a fallback mechanism to keep the UI and UX consistent when the native effect isn't there.

Conclusion
The goal is to bring native UI primitives into our own React Native repo.
Expo supports every direction here, because you can pick the right tool for each part of the app:
- Use React Native
ViewandTextwhen you need cross-platform consistency. - Use Expo UI when you want the highest native fidelity.
- Use
react-native-skia(opens in a new tab) for fully custom rendering and graphics. - Use
react-native-webgpu(opens in a new tab) for 3D experiences.
In its current version, Expo UI focuses on SwiftUI first; Jetpack Compose support for Android is coming in later updates.
