iPhone Duo Support in Expo and React Native
Apple's first foldable ships on October 23 with a 5.4-inch outer display and a 7.6-inch inner one. Your Expo app will run on it without changes. How good it looks there depends on which SDK you build with and on some layout assumptions most React Native apps make without noticing. This guide covers what changes, what you can fix today, and what is still waiting on Apple and Expo.
The short version
There is no fold API in React Native, and you mostly don't need one. Treat the Duo as a device whose window changes size: get the size from useWindowDimensions, not from the device model or a portrait lock. Read each safe area edge separately, because the insets on opposite sides are no longer equal. Prefer native navigation (NativeTabs, native stack), which picks up Apple's layout changes for free. The full-screen experience on the inner display needs the iOS 27.1 SDK. Xcode 27.1 hasn't shipped yet, and neither have EAS build images for it. So do the layout work now, then rebuild once the tooling lands.
Status as of September 16, 2026
Xcode 27 reached release candidate on September 9. Apple says the Xcode 27.1 beta, which includes the iPhone Duo simulator, is coming later in September. Expo SDK 58 is in beta. The documented EAS images top out at Xcode 26.6. I'll update this page when those change.
1. What the Duo actually changes
From an app's point of view, the Duo is one device that behaves like two, and it can switch between them while your app is running. Apple lists four poses to support:
- Closed, portrait: a normal iPhone. Compact width, regular height.
- Closed, landscape: a normal iPhone on its side. Compact in both directions.
- Open, vertical: a tall, nearly square window.
- Open, horizontal: a wide window.
When the Duo is open, the inner display is regular in both size classes, the same as an iPad. It also supports Split View with a second app, so your window can be a fraction of the screen. Three details matter most for React Native apps:
- The inner display ignores orientation locks. Apple says it doesn't honor
supportedInterfaceOrientations. - Safe areas are asymmetric. In landscape and Split View, system buttons can stack down one side, so the left and right insets differ.
- Some parts of the screen are reserved. The fold is one reserved region, and the under-display FaceTime camera is another.
2. Your SDK decides how much screen you get
Apple doesn't require a rebuild for the Duo. It changes how much of the inner display an app gets, based on the SDK the app was built with:
- iOS 26 SDK or earlier: the app stays in a familiar iPhone-sized area on the inner display, with empty space around it. When the Duo is closed, the app uses the space beside the status bar and camera.
- iOS 27 SDK: the app extends further across the inner display, but not all of it.
- iOS 27.1 SDK: the app reaches the edges of the screen, and standard navigation and toolbar buttons move into vertical arrangements.
With Expo, the SDK comes from the Xcode on the build machine, not from your Expo SDK version. On EAS Build, that is the build image. According to Expo's infrastructure docs today, the sdk-57 and latest aliases resolve to macos-tahoe-26.5-xcode-26.6, which uses the iOS 26 SDK. So an SDK 57 app built on EAS today gets the most conservative treatment.
Once Expo publishes an Xcode 27.1 image, pin it on your iOS build profile:
// eas.json
{
"build": {
"production": {
"ios": {
"image": "<xcode-27.1 image name from Expo's infrastructure docs>"
}
}
}
}For local builds, xcode-select -p shows which Xcode npx expo run:ios will use.
Don't jump to a beta Xcode on an older Expo SDK
An open issue reports that SDK 56 projects fail to compile in ExpoModulesJSI on the Xcode 27 beta. Expect a new Xcode to need a matching Expo SDK. Check the Expo changelog before you switch production builds, and do the upgrade on a branch. My SDK 57 upgrade guide covers that process.
The App Store deadline is separate. From April 2027, uploads must be built with the iOS 27 SDK or later. That rule is about compiler versions, not foldable support, so meeting it doesn't mean your app is optimized for the Duo.
3. Stop relying on the orientation lock
Many Expo apps set "orientation": "portrait" in app.json and then assume a tall, narrow screen everywhere. On the Duo's outer display, that still works. On the inner display, the lock is ignored, and the app has to handle a wide window anyway.
// app.json
{
"expo": {
"orientation": "portrait", // honoured on the outer display only
"ios": {
"requireFullScreen": false // the default; keep it
}
}
}ios.requireFullScreen sets UIRequiresFullScreen, which opts the app out of Split View. Apple says the Duo still honors it, but the app still resizes when the Duo opens or closes. It won't keep your window at a fixed size, so setting it only takes away a feature from your users. The same applies to expo-screen-orientation locks: keep them if you like, but don't make your layout depend on them.
4. Lay out from window size, not device
Apple's main advice is to make layout decisions from size classes, not from the device or the orientation. React Native has no size-class API, but useWindowDimensions re-renders whenever the window changes size, and that covers the same cases.
First, check for code that reads the size once and never updates:
// Breaks on the Duo: read once at import time, never updated
const { width } = Dimensions.get("window");
const styles = StyleSheet.create({ card: { width: width - 32 } });
// Also breaks: the window can change without the device rotating
const isTablet = Device.deviceType === Device.DeviceType.TABLET;Then base your breakpoints on what your content needs, not on how big the device is. For a list-and-detail screen, show two columns once each column has room to work:
// hooks/useLayoutMode.ts
import { useWindowDimensions } from "react-native";
const MIN_COLUMN = 320; // the narrowest your detail pane still reads well
const GUTTER = 24;
export function useLayoutMode() {
const { width } = useWindowDimensions();
const columns = width >= MIN_COLUMN * 2 + GUTTER ? 2 : 1;
return { width, columns };
}export default function Inbox() {
const { columns } = useLayoutMode();
return columns === 2 ? (
<View style={{ flex: 1, flexDirection: "row" }}>
<MessageList style={{ width: 360 }} />
<MessageDetail style={{ flex: 1 }} />
</View>
) : (
<MessageList style={{ flex: 1 }} />
);
}A width-based breakpoint also handles Split View on the inner display, iPad multitasking, and Android foldables, with no special cases. Also keep the selected item in state or in the route, not in the component tree. When the Duo closes, the app should switch to one column and still show the same message.
Don't detect the Duo by model name
At the time of writing, the expo-device PR that maps iPhone19,4 to "iPhone Duo" is still open, so Device.modelName won't reliably report it yet. Even once it does, a Duo in closed mode needs the phone layout. The model name tells you what the hardware is, not how big your window is.
5. Handle each safe area edge separately
On the Duo, safe area insets are often different on opposite sides. In landscape and Split View, system buttons stack down one edge. Code that doubles the left inset to account for the right one will overlap those buttons:
import { useSafeAreaInsets } from "react-native-safe-area-context";
const insets = useSafeAreaInsets();
// Wrong: assumes left === right
const brokenWidth = width - insets.left * 2;
// Right: subtract each edge on its own
const contentWidth = width - insets.left - insets.right;
<View style={{
paddingTop: insets.top,
paddingBottom: insets.bottom,
paddingLeft: insets.left,
paddingRight: insets.right,
}} />Apple's rule: controls stay inside the safe area, backgrounds extend past it. Draw full-bleed artwork and colors to the edges, and inset only the parts people read or tap. SafeAreaView from react-native-safe-area-context with explicit edges does this well. The SafeAreaView built into React Native is iOS-only and doesn't let you choose edges. Expo's safe areas guide covers the setup.
Keep interactive controls out of the middle of the inner display when your window spans the fold. Scrolling content handles the crease fine. A button sitting right on it doesn't.
6. Use native navigation where you can
Much of the Duo adaptation happens inside UIKit and SwiftUI. Apple says tab bars, split views, sheets, popovers, context menus, and alerts all adjust to each pose. In the 27.1 SDK, tab bars and toolbars can move to the side of the display.
An Expo app gets this behavior only when the screen actually uses a native component. A tab bar drawn in JavaScript with @react-navigation/bottom-tabs stays exactly where you put it. Expo Router's native tabs use the system tab bar, so they adapt:
// app/(tabs)/_layout.tsx
// SDK 58+: "expo-router/native-tabs"
// SDK 55-57: "expo-router/unstable-native-tabs"
import { NativeTabs } from "expo-router/native-tabs";
export default function TabLayout() {
return (
<NativeTabs>
<NativeTabs.Trigger name="index">
<NativeTabs.Trigger.Label>Home</NativeTabs.Trigger.Label>
<NativeTabs.Trigger.Icon sf="house.fill" md="home" />
</NativeTabs.Trigger>
<NativeTabs.Trigger name="settings">
<NativeTabs.Trigger.Label>Settings</NativeTabs.Trigger.Label>
<NativeTabs.Trigger.Icon sf="gear" md="settings" />
</NativeTabs.Trigger>
</NativeTabs>
);
}The same applies to the native stack, which Expo Router's Stack uses by default: its headers and sheets are system components. Expo UI's SwiftUI components render real SwiftUI, so they adapt the way SwiftUI does. If you have auth guards on these tabs, the setup in Expo Router protected routes carries over unchanged.
One catch: once the system can move the tab bar, don't hard-code its height or assume it sits at the bottom. Pad your content using the safe area insets instead.
7. What about the fold itself?
Apple's SDK now includes hinge and reserved-region APIs. SwiftUI has onHingeChange and ReservedRegion, and UIKit has UIHingeInteraction and UIViewReservedRegion. React Native doesn't expose any of them, and at the time of writing Expo doesn't either.
Apple itself says hinge state is for interactions and effects, not for layout. If you have a real use for it, like a camera app that changes mode when half-folded, write a small Expo module that emits hinge events to JavaScript. Wait until Xcode 27.1 is out so you can test against the real API. Don't build your screen layout on fold events, because size changes already cover that.
8. Testing, now and once Xcode 27.1 lands
Before the Duo simulator exists, you can still test most of this:
- iPad simulator in Split View and Slide Over. Resizing the window live tests
useWindowDimensions, your breakpoint, and whether the selected item survives the change. - Rotate a portrait-locked build on iPad with
ios.supportsTabletenabled. It's the closest stand-in for the inner display ignoring your orientation lock. - An iPhone simulator in landscape to test uneven insets around the Dynamic Island.
Once Xcode 27.1 is available, Device Hub will include an iPhone Duo simulator with buttons to open, close, rotate, and fold the device. Test each of these while the app is running, not only at launch:
- Closed portrait, then open. Check that the layout switches to two columns and keeps your state.
- Open, then closed. Check that the layout goes back to one column without losing your place.
- Partially folded, with nothing tappable sitting on the crease.
- Both rotations while open, even if the app is portrait-locked.
- Split View with another app on each side. Drag the home indicator to move your app, and watch the insets.
- Text inputs with the keyboard open in every pose.
Then build a TestFlight version with the 27.1 SDK. The EAS Build and Submit checklist covers that flow.
9. Production checklist
- No layout values from
Dimensions.getat import time;useWindowDimensionseverywhere layout depends on size. - Breakpoints come from content width, never from device model or
deviceType. - Layout doesn't depend on
orientationorexpo-screen-orientationlocks. ios.requireFullScreenleft atfalseunless you truly can't support Split View.- Safe area insets applied per edge; no
insets.left * 2. - Backgrounds full-bleed, controls inside the safe area and away from the fold.
- Tabs and headers use native components; no hard-coded tab bar height.
- Screen state survives a resize from one column to two and back.
- iOS builds pinned to an Xcode 27.1 image once Expo publishes one, on a matching Expo SDK.
- Every pose tested in the Device Hub simulator before the next App Store release.
Sources
Apple Newsroom: Apple unveils iPhone Duo | Apple Tech Talk: Prepare your app for iPhone Duo | Gadget Hacks: iPhone Duo compatibility tiers | The Mac Observer: April 2027 SDK rule | Expo: Build server infrastructure | Expo Router: Native tabs | Expo: app.json reference | expo/expo#49924 | expo/expo#46876 | Code with Beto: Is your app ready for iPhone Duo?
Related tutorials
Need this built for you? I take on contract Expo and React Native work — see how I can help.