Docs

Dev tools

Expo SDKExpo CLIExpo MCPExpo GoSnackOrbit

Services

WorkflowsBuildSubmitUpdateHostingLaunchObserve
new
Simulators
preview

Explore

ChangelogExpo Services (EAS)ContactAI
EnterprisePricingBlog
50K
Log in
Sign up

Site footer

Expo

Newsletter

Stay in touch with all things Expo

Product

  • Star us on GitHub
  • Expo CLI on GitHub
  • Expo Services (EAS)
  • EAS CLI on GitHub
  • Expo Go
  • Expo Orbit
  • Snack

Resources

  • Documentation
  • Blog
  • Articles
  • Demos
  • Changelog
  • Support
  • Trust Center
  • Join Discord

Solutions

  • Enterprise
  • Startup
  • Solo devs
  • React web devs
  • E-commerce
  • Crypto
  • Finserv
  • QSR

Company

  • Home
  • Pricing
  • Customers
  • Consultants
  • About
  • Branding
  • Careers

Legal

  • Terms of service
  • Acceptable use policy
  • Privacy policy
  • Privacy explained
  • Cookie policy
  • Security & Compliance
  • Enterprise trust
  • Community guidelines
© 2026 650 Industries, Inc.
All systems operational

October 23, 2026::Users·Development

Supporting iPhone Duo with Expo: a developer guide

How to ready your app for iPhone Duo: SDK 58 changes, Expo Router, new safe area insets, and new hinge and reserved region APIs.

Alex Hunt

Alex Hunt

Engineering

Christian Falch

Christian Falch

Engineering

Jakub Tkacz

Jakub Tkacz

Engineering

Illustration of a folded device blueprint with the Expo logo

Contents

  • What’s different?
  • Requirements
  • High level guidelines
  • Expo Router
  • Recommended: Use native navigation bars
  • Header buttons in the rail
  • Split views
  • Sidebar or tab bar on the open display
  • JavaScript tab bars (not recommended)
  • Safe Areas
  • Additional APIs
  • Reserved regions
  • Hinge
  • Detecting and disabling the rail
  • Limitations
  • Testing
  • Other iOS 27 additions in SDK 58
  • Further reading

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 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>.

Where the system bars go: a rail on the right edge when folded, on the outer edge of each window in Split View, and horizontal bars again on the open display in portrait.

The good news is if you’ve been building your app with Expo Router and NativeTabs, 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.

Xcode 27 also replaces Simulator.app with 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 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.
  • 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.
  • 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.
  • 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.

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) — 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 and NativeTabs. 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).

The same native toolbar and tab bar on the open display in landscape (left) and portrait (right). The system automatically moves them between the rail and the horizontal bars on rotation.

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).

Split views

Expo Router's <SplitView> 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).

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 system split view folded, partially open, and open. The columns settle on either side of the fold, and the open message stays open through every pose. SplitView is experimental in SDK 58.

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 prop picks one when only one of them can show: 'sidebar', 'tabBar', or 'automatic', which leaves it to the system.

NativeTabs on the open display with preferredPlacement set to 'sidebar' (left) and 'tabBar' (right).

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 hard time: A JS Stack header and a TabList tab bar folded (left) and on the open display in portrait (right). These do not follow the system guidelines and minimally avoid the safe areas, including a large empty top inset in unfolded portrait.

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 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:

(Note: contentInsetAdjustmentBehavior="automatic" will handle this alone in a future React Native patch: #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 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 same mail app in the left window of Split View. The rail moves to the left edge, and the left inset takes the 84pt.

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).

Additional APIs

Reserved regions

iOS 27.1 introduces a new concept separate to safe areas: 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.

The reserved regions on the open display, shaded over a mail app: the fold and the inner camera are inactive while the device is flat and the camera is off, and the status bar's region is active and has grown to take in the Dynamic Island while audio plays.

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

Use 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:

(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 library reports the hinge angle and its status ( closed, partiallyOpen, fullyOpen, or unknown).

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

The hinge angle and status from expo-hinge on the partially open display, with an ArrangementView putting a panel on each side of the fold.

For a SwiftUI view in Expo UI, a new onHingeChange 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.

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!).

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).

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

The open display with the rail (left) and with the vertical bar disabled (right).

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). 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).

Testing

Test on the iPhone Duo simulator in Xcode 27.1, folded and open, and in each orientation your app supports. 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 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 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 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 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, Apple's developer overview, including the rules for which bar items the rail shows.
  • Apple's tech talks: Prepare your app for iPhone Duo, Raise the bar with iPhone Duo, Strike a pose with adaptive layouts on iPhone Duo, and Design for iPhone Duo.
  • Expo docs: Safe areas, Native tabs, Stack toolbar, expo-hinge, and ArrangementView.
Expo SDK
iPhone Duo

Share article

Related articles

All posts →
Introducing expo-app-intents: Siri, Shortcuts, Spotlight, and Apple Intelligence for Expo apps

October 19, 2026

Introducing expo-app-intents: Siri, Shortcuts, Spotlight, and Apple Intelligence for Expo apps

Xcode 27 replaced Simulator.app. Expo CLI and expo-device-hub already know.

October 20, 2026

Xcode 27 replaced Simulator.app. Expo CLI and expo-device-hub already know.

src/app/_layout.tsx
<NativeTabs preferredPlacement="sidebar">
<NativeTabs.Trigger name="index">
<Icon sf="play.circle" />
<Label>Home</Label>
</NativeTabs.Trigger>
{/* ... */}
</NativeTabs>
npx expo install react-native-safe-area-context
// 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>
);
}
npx expo install react-native-reserved-regions
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 />
);
npx expo install expo-hinge
import { useHinge } from 'expo-hinge';
export function HingeReadout() {
const hinge = useHinge();
return <Text>{hinge ? `${Math.round(hinge.angle)}° (${hinge.status})` : 'No hinge'}</Text>;
}
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>;
}
import { StatusBar } from 'expo-status-bar';
export default function Player() {
return (
<>
<StatusBar verticalBarBehaviorIOS="disabled" />
<VideoView player={player} style={StyleSheet.absoluteFill} />
</>
);
}