---
title: 'Supporting iPhone Duo with Expo: a developer guide'
authors: Alex Hunt, Christian Falch, Jakub Tkacz
published: October 23, 2026
categories: Users, Development
tags: Expo SDK, iPhone Duo
---

iPhone Duo, Apple's first foldable iPhone, ships on October 23, and is the first genuinely new iPhone form factor in years. It has a compact outer display for use folded and a large inner display that opens like a book, with a hinge that people hold at any angle, stand on a table, or fold flat.

Expo [SDK 58](https://expo.dev/changelog/sdk-58) is built for iOS 27 and iPhone Duo on day one, and with a few changes your app can feel at home on both displays and in every pose.

## What’s different?

iPhone Duo adds three major layout concepts to iOS:

- **Vertical navigation in most device poses.** iOS moves the system status bar, tab bar, and toolbar controls into a **vertical rail** on one edge, applying when the device is in a book-like orientation. In other poses, iOS keeps horizontal bars.
- **New sizes and orientations.** iPhone Duo can be opened, partially opened, and rotated, requiring dynamic sizing of your app. Users can also position your app alongside another in Split View.
- **Split layouts** for iPad-like content arrangement when unfolded. You can choose for a navbar to adapt to a sidebar layout via `preferredPlacement`, or split content into a two pane view via `<ArrangementView>`.

![A mail app on iPhone Duo in three states. Folded, the back button, five toolbar icons, and the tab bar stack in a vertical rail on the right edge. In the left window of Split View, the same rail sits on the left edge of the window. Open in portrait, the toolbar runs along the top and the tab bar along the bottom.](https://cdn.sanity.io/images/9r24npb8/production/69e3a68a3f8433fc4a205bd489981696f10771c0-1800x808.png)

**The good news** is if you’ve been building your app with Expo Router and [`NativeTabs`](https://docs.expo.dev/versions/v58.0.0/sdk/router/native-tabs/), many of these concerns are handled for you via the native navigator. The work that remains is auditing your screens and applying additional safe areas, and making decisions for your app’s internal layout.

Additionally, iOS 27.1 provides new APIs for the device hinge, the rail, and reserved regions, all available in SDK 58 and covered further down in this guide.

## Requirements

You'll need both Expo SDK 58 and Xcode 27.1, which ships with the iOS 27.1 simulator runtime and iPhone Duo simulator. On EAS Build, use the `sdk-58` or `latest` [image](https://docs.expo.dev/build-reference/infrastructure/).

Xcode 27 also replaces Simulator.app with [Device Hub](https://expo.dev/blog/xcode-27-device-hub-expo-device-hub) (**Xcode** > **Open Developer Tool** > **Device Hub**), which provides a 3D simulator for iPhone Duo where you can open and rotate to various poses as well as freely adjust the hinge angle.

## High level guidelines

Apple's [guidelines for iPhone Duo](https://developer.apple.com/design/human-interface-guidelines/designing-for-iphone-duo) introduce these new design concepts, and emphasize you are still designing for iPhone.

Progressively enhancing your app starts by laying out content dynamically based on the available space. Treat the folded display as compact width and the open display as regular width, let the layout widen rather than switch designs. Keep the same features and state on both displays.

In an Expo app, here's what we recommend:

- **Let iOS draw the navigation.** Expo Router's `Stack` and `NativeTabs` use the system's native navigation and controls. iOS handles where controls and tabs are placed for you, rail included. Custom headers or tab bars in JavaScript, by contrast, will require additional work. See [Expo Router](https://expo.dev/blog/supporting-iphone-duo#expo-router).
- **Cover your view contents with SafeAreaView.** iPhone Duo brings new safe area offsets, principally from the rail. If your app isn't set up to handle horizontal insets yet, now is the time to make more use of `react-native-safe-area-context` across your screens. See [Safe Areas](https://expo.dev/blog/supporting-iphone-duo#safe-areas).
- **Test every pose.** Fold, unfold, rotate, and enter Split View on the iPhone Duo simulator, and check that a screen stays open with its state as the device folds. See [Testing](https://expo.dev/blog/supporting-iphone-duo#testing).
- **Decide what to tweak.** This is app specific, but next you can layer in APIs such as `<ArrangementView>` and `preferredPlacement` to make better use of the unfolded space for your app. Some apps may want to disable the vertical bar in certain states. You could even customise which side of a partially folded screen you'd prefer modals to open on. See [Additional APIs](https://expo.dev/blog/supporting-iphone-duo#additional-apis).

## Expo Router

We strongly recommend using Expo Router with native navigation, as this gives the most behaviors from the system for free.

### Recommended: Use native navigation bars

On iPhone Duo the navigation bar, tab bar, and toolbar are not fixed to the top and bottom. iOS moves them into the rail when the device is folded or open in landscape, back to horizontal bars in portrait, and onto the outer edge of each window in Split View, animating each change and laying the bar items out again as it goes!

Furthermore, you have the vertical Dynamic Island also taking up space in the rail when a Live Activity is ongoing (see [Reserved Regions](https://expo.dev/blog/supporting-iphone-duo#reserved-regions)) — iOS shrinks toolbars to an overflow menu to follow this, too.

**TLDR**: Use the native controls and let the system handle this all for you, by using Expo Router's [`Stack`](https://docs.expo.dev/versions/v58.0.0/sdk/router/stack/) and [`NativeTabs`](https://docs.expo.dev/versions/v58.0.0/sdk/router/native-tabs/). You'll have a hard time if fighting to implement these behaviors manually, and Apple's guidance is to let the system place the bars ([Vertical controls](https://developer.apple.com/design/human-interface-guidelines/designing-for-iphone-duo#Vertical-controls)).

![A mail app on the open iPhone Duo in landscape and portrait. In landscape, the clock, back button, five toolbar icons, and the tab bar stack in a rail on the right edge beside the message. In portrait, the toolbar icons run along the top and the tab bar along the bottom.](https://cdn.sanity.io/images/9r24npb8/production/6256c5fa7ac439a26a15004f97c07fd70870dd4c-1800x1587.png)

### Header buttons in the rail

An automatic behaviour that might be surprising is that iOS stacks your header and toolbar items in one column. This is semantic, with Back or Close first, then prominent actions, and potentially an overflow menu.

iOS also automatically handles if a menu item shouldn't do this: text-only items stay in a horizontal bar, and custom views are left out ([Organize items in your bars](https://developer.apple.com/documentation/technologyoverviews/preparing-your-app-for-iphone-duo#Organize-items-in-your-bars)).

### Split views

Expo Router's [`<SplitView>`](https://docs.expo.dev/versions/v58.0.0/sdk/router/split-view/) wraps the system split view. Apple recommends a split view when there is enough horizontal width, which can contain two inner panes, like Mail, or three, like Keynote ([Split views](https://developer.apple.com/design/human-interface-guidelines/designing-for-iphone-duo#Split-views)).

On the Duo, the split view shows one column when folded and the columns side by side when open. It also handles automatically avoiding the fold region when the device is partially folded.

![A mail app on iPhone Duo folded, partially open, and open. Folded, the open message fills the display. Partially open, the mailboxes and inbox sit left of the fold and the message right of it. Open, the inbox and message sit side by side.](https://cdn.sanity.io/images/9r24npb8/production/9a4ff29da0bcf6ec142926c0e7dea35dedbb9a3a-2400x2360.png)

### Sidebar or tab bar on the open display

On the open display in landscape, iOS 27 can show `NativeTabs` as the tab bar in the rail or as a sidebar, the same choice iPad offers. The [`preferredPlacement`](https://docs.expo.dev/versions/v58.0.0/sdk/router/native-tabs/#preferredplacement) prop picks one when only one of them can show: `'sidebar'`, `'tabBar'`, or `'automatic'`, which leaves it to the system.

<!-- src/app/_layout.tsx -->
```tsx
<NativeTabs preferredPlacement="sidebar">
  <NativeTabs.Trigger name="index">
    <Icon sf="play.circle" />
    <Label>Home</Label>
  </NativeTabs.Trigger>
  {/* ... */}
</NativeTabs>
```

![A music app on the open iPhone Duo in landscape, twice. On the left, the five tabs run down a sidebar on the left of the display, with a button above them to hide it. On the right, the same tabs stack in a vertical rail on the right edge, under the clock.](https://cdn.sanity.io/images/9r24npb8/production/b686b870af1a765cc36018908beb5fd8e4fa0fb4-1800x897.png)

### JavaScript tab bars (not recommended)

To repeat one more time, apps with custom navs will have a hard time if you're trying to mimic the placement and behaviours of the system bars.

For iPhone Duo, we've added minimal compat behaviour for custom JS `<Stack>` header and `<TabList>` components. They will render classically (not following any iPhone Duo conventions), inside the device safe areas.

![A placeholder app on iPhone Duo folded and open in portrait. Each shows a JavaScript stack header with a round back button and a title, gray boxes for content, and a tab bar of three pills along the bottom. Folded, the header starts below the clock in the right rail, and the tab bar ends at the rail.](https://cdn.sanity.io/images/9r24npb8/production/32f04a5194439ded106ca2be2209a90e1b873439-1800x1435.png)

For now, we intentionally do not support a `<TabList>` component hosted in the vertical rail position. This can theoretically be built yourself using the vertical bar and reserved region APIs — and handling the various states needed — but is not recommended.

## Safe Areas

[`SafeAreaView`](https://docs.expo.dev/versions/v58.0.0/sdk/safe-area-context/) is your friend. Most apps today focus on applying `<SafeAreaView>` predominantly for top and bottom system insets. On iPhone Duo, this assumption changes due to the rail, which needs either an 84pt left or right inset.

**Do audit all your app's screens**, to check and tweak which content should sit away from or under the rail.

Inside a scroll view, wrap the content rather than the scroll view, so the background scrolls edge to edge while the content stays clear of the rail:

```bash
npx expo install react-native-safe-area-context
```

```tsx
// src/app/article.tsx
import { ScrollView, Text } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';

export default function ArticleScreen() {
  return (
    <ScrollView contentInsetAdjustmentBehavior="automatic">
      <SafeAreaView edges={['left', 'right']} style={{ padding: 16 }}>
        <Text>Content stays clear of the rail on either edge.</Text>
      </SafeAreaView>
    </ScrollView>
  );
}
```

(Note: `contentInsetAdjustmentBehavior="automatic"` will handle this alone in a future React Native patch: [#59194](https://github.com/react/react-native/pull/59194))

Easy to miss:

- **Inside scroll views**: a horizontal carousel or full-bleed row inside a full-width scroll view needs its own horizontal end padding.
- **Absolutely positioned views**: floating buttons and toasts need `right: insets.right + 16`, not `right: 16`.
- **Window size**: it changes as the device folds and in Split View, where the left window is 469pt wide. Read [`useWindowDimensions`](https://reactnative.dev/docs/usewindowdimensions) at render time or let flex decide; a value cached when a toast was shown is stale after the window grows.
- **Right-to-left languages**: the rail stays on the camera's physical edge, so read both `left` and `right` rather than assuming a trailing edge.
- **The tab bar**: inside a `NativeTabs` screen, `insets.bottom` includes the tab bar only while the bar runs along the bottom, so do not add a fixed tab bar height to it.

Remember that the horizontal inset can apply on the left, too! Split View on the open display is an important test case.

![The open iPhone Duo in Split View with a mail inbox in the left window and Safari in the right. The mail app's back button, toolbar buttons, and tab bar sit in a vertical rail on the left edge of its window; Safari's controls sit in a rail on the right edge of the display.](https://cdn.sanity.io/images/9r24npb8/production/2fd4bee0abb0e96cb706e5e944947fac9b38b4dc-1400x1038.png)

The space beside the rail is asymmetrical by design. Pad the content container by the insets, not the screen's root view: a background or header image can run edge to edge under the rail while scrolling content stays inset, as long as nothing interactive sits there ([Vertical controls](https://developer.apple.com/design/human-interface-guidelines/designing-for-iphone-duo#Vertical-controls)).

## Additional APIs

### Reserved regions

iOS 27.1 introduces a new concept separate to safe areas: [reserved regions](https://developer.apple.com/design/human-interface-guidelines/designing-for-iphone-duo#Reserved-regions).

Reserved regions are a set of layout boxes for the active screen that may be reserved by the system — e.g. the screen fold, camera cutouts, and Dynamic Island. These can either be active or inactive (e.g. if the inner display camera is in use or not). The Dynamic Island region may also grow/shrink vertically.

![A mail app on the open iPhone Duo in landscape, with the reserved regions UIKit reports shaded over it. A gray strip down the middle is labeled fold, inactive. A small gray rectangle near the top right is labeled camera, inactive. A blue block at the top of the right edge, around the expanded Dynamic Island, the clock, and the Wi-Fi icon, is labeled status bar plus Dynamic Island, active.](https://cdn.sanity.io/images/9r24npb8/production/0eccd01c581eefdf09fa9337471cce93c2e2b10d-1800x1334.png)

Again, the system handles most of this: alerts, menus, and sheets move off the fold on their own.

Use [`react-native-reserved-regions`](https://github.com/appandflow/react-native-reserved-regions) to observe reserved regions from JavaScript. Each region has a `kind`, `division` or `occlusion`, and a `frame` in points relative to the provider, margins included. In this contrived example, laying out up to `frame.x` clears the fold:

```bash
npx expo install react-native-reserved-regions
```

```tsx
const fold = useReservedRegions().find((region) => region.kind === 'division');

return fold ? (
  <View style={{ flex: 1, flexDirection: 'row' }}>
    <List style={{ width: fold.frame.x }} />
    <Detail style={{ flex: 1, marginLeft: fold.frame.width }} />
  </View>
) : (
  <Detail />
);
```

(Don't do this, use `<ArrangementView>`)

**Real use cases**

- Avoid the system controls in a custom UI, such as a game.
- Custom grid layouts: Apple suggests an even number of grid columns, so the fold falls between them.

### Hinge

The new [`expo-hinge`](https://docs.expo.dev/versions/v58.0.0/sdk/hinge/) library reports the hinge angle and its status ( `closed`, `partiallyOpen`, `fullyOpen`, or `unknown`).

```bash
npx expo install expo-hinge
```

```tsx
import { useHinge } from 'expo-hinge';

export function HingeReadout() {
  const hinge = useHinge();
  return <Text>{hinge ? `${Math.round(hinge.angle)}° (${hinge.status})` : 'No hinge'}</Text>;
}
```

`getHinge()` and `addHingeListener()` are the imperative forms.

![A partially open iPhone Duo showing a hinge demo screen. The left panel reads 120 degrees, partially open. The right panel lists the angle, the system status, and the session's lowest and highest angles.](https://cdn.sanity.io/images/9r24npb8/production/3a9430be5497181bb79016e6f36482db1fcab4d0-1400x974.png)

For a SwiftUI view in Expo UI, a new [`onHingeChange`](https://docs.expo.dev/versions/v58.0.0/sdk/ui/swift-ui/modifiers/#onhingechangehandler) modifier is available, and can run on the UI thread via from `useWorkletCallback`.

`expo-hinge` also supports Reanimated, as well as Android 11 and later foldables.

### Detecting and disabling the rail

Finally, iOS offers specific APIs for the device rail, and we've exposed these via [`expo-status-bar`](https://docs.expo.dev/versions/v58.0.0/sdk/status-bar/).

`useVerticalBarEdgeIOS()` returns `'leading'`, `'trailing'`, or `null`, based on if the system has drawn a vertical bar. (**Therefore**, iPhone Duo can report `null` if it's open in portrait — this isn't a Duo-detecting API!).

```tsx
import { useVerticalBarEdgeIOS } from 'expo-status-bar';

export function BadlyAdvisedCustomHeader({ children }: { children: React.ReactNode }) {
  const edge = useVerticalBarEdgeIOS();
  if (edge === null) {
    return <View style={styles.row}>{children}</View>;
  }
  return <View style={[styles.column, edge === 'trailing' && styles.trailing]}>{children}</View>;
}
```

**You can disable the rail(!)**

Apple reserves this for screens that horizontal bars serve better, such as a video player or a calculator, and asks that it be a stable choice per screen ([`preferredVerticalBarBehavior`](https://developer.apple.com/documentation/uikit/uiviewcontroller/preferredverticalbarbehavior)).

```tsx
import { StatusBar } from 'expo-status-bar';

export default function Player() {
  return (
    <>
      <StatusBar verticalBarBehaviorIOS="disabled" />
      <VideoView player={player} style={StyleSheet.absoluteFill} />
    </>
  );
}
```

The open display in portrait is always in this state, so a screen that handles disabling the rail has handled portrait too.

![An empty screen with a navigation header on the open iPhone Duo, twice. On the left, the status bar, back button, and tab bar sit in a vertical rail on the right edge. On the right, with the vertical bar disabled, the status bar and back button run along the top and the tab bar along the bottom.](https://cdn.sanity.io/images/9r24npb8/production/3cc530b4014f753d40a21e3325066f366a892045-1800x933.png)

## Limitations

- **StandBy is unavailable on iPhone Duo simulator.** Xcode 27.1's release notes list StandBy and most app extensions as unavailable in the iPhone Duo simulator runtime, so check widgets and other extensions on a device.
- **Orientation locks are a preference on iOS 27.** Wherever an iPhone app can be resized, in iPhone Mirroring or on iPad, the system treats a supported orientation as a preference, `lockAsync` can be refused, and `requireFullScreen` no longer opts your app out ([Orientation locks on iOS 27 and later](https://docs.expo.dev/versions/v58.0.0/sdk/screen-orientation/#orientation-locks-on-ios-27-and-later)). On iPhone Duo itself, Apple says you can lock to portrait or landscape, as long as you fill the screen as the pose changes ([Best practices](https://developer.apple.com/design/human-interface-guidelines/designing-for-iphone-duo#Best-practices)).

## Testing

Test on the iPhone Duo simulator in Xcode 27.1, folded and open, and in each orientation your app supports. [Device Hub](https://developer.apple.com/documentation/xcode/device-hub) folds the simulator to any angle and shows the display the device is using. Walk every screen in each pose, and rotate the device in each pose:

- **Folded**: nothing important sits under the rail, and every button responds. Header buttons you expect in the rail are there, with the rest in the overflow menu.
- **Open**: the layout does not jump to a different design, and nothing runs under the rail. In portrait, the bars are back at the top and bottom and the layout still holds.
- **Partially open**: nothing you need to read or tap sits in the fold, and sheets and popovers land on one side of it.
- **Split View**: with your app in each window on the open display, the content stays clear of the rail, which sits on the left edge of the left window.
- **Folding with a screen open**: the screen stays open at the same scroll position, with the same state and the same actions on both displays.
- **A standard iPhone**: nothing changes.

## Other iOS 27 additions in SDK 58

- **App Intents.** [`expo-app-intents`](https://docs.expo.dev/versions/v58.0.0/sdk/app-intents/) exposes your app's actions and content to Siri, Shortcuts, Spotlight, and Apple Intelligence. The library is in alpha. Some schemas need a newer iOS version than their domain, and `.mail.openDraft` needs iOS 27. The [SDK 58 post](https://expo.dev/changelog/sdk-58#app-intents-expose-your-app-to-siri-and-apple-intelligence) walks through it.

## Further reading

We recommend that you point a coding agent at this blog post, have it open a PR with the inset fixes, and run the build on the iPhone Duo simulator through [Expo CICD Workflows](https://expo.dev/services/workflows) before you merge. Let us know how that goes. Obviously having the Expo MCP connected will be helpful as well.

And here are some other helpful resources for iPhone Duo design and development best practices.

- [Designing for iPhone Duo](https://developer.apple.com/design/human-interface-guidelines/designing-for-iphone-duo) in the Human Interface Guidelines, for the design rules behind this guide: the rail, reserved regions, arrangement views, and device poses.
- [Preparing your app for iPhone Duo](https://developer.apple.com/documentation/technologyoverviews/preparing-your-app-for-iphone-duo), Apple's developer overview, including the rules for which bar items the rail shows.
- Apple's tech talks: [Prepare your app for iPhone Duo](https://developer.apple.com/videos/play/tech-talks/111461), [Raise the bar with iPhone Duo](https://developer.apple.com/videos/play/tech-talks/111462), [Strike a pose with adaptive layouts on iPhone Duo](https://developer.apple.com/videos/play/tech-talks/111463), and [Design for iPhone Duo](https://developer.apple.com/videos/play/tech-talks/111466).
- Expo docs: [Safe areas](https://docs.expo.dev/develop/user-interface/safe-areas/), [Native tabs](https://docs.expo.dev/router/advanced/native-tabs/), [Stack toolbar](https://docs.expo.dev/router/advanced/stack-toolbar/), [`expo-hinge`](https://docs.expo.dev/versions/v58.0.0/sdk/hinge/), and [`ArrangementView`](https://docs.expo.dev/versions/v58.0.0/sdk/ui/swift-ui/arrangementview/).