Articles · How toUpdated October 2026

You can add a bottom sheet with no library at all, and Apple does not call it a bottom sheet.

Set the presentation option on an Expo Router screen to formSheet and you have a real iOS sheet that drags between heights, with nothing installed, because those options are part of Expo Router. If you are not using Expo Router, React Native's own Modal component takes a presentationStyle of pageSheet or formSheet and also needs no dependency, though it gives you no control over the height. The third route is a package, Expo UI, and its advantage is that it hands you a component instead of a screen. Apple's own word for all of this is sheet, and the word for each height it rests at is detent. If you are earlier in the process than this, start at make an app.

What follows is what Apple documents about sheets and detents, what each of the three options actually exposes, a row by row map of the iOS behaviour against the options you are given, and the five things that go wrong. Every claim comes from Apple, React Native or Expo documentation read on 23 October 2026.

Start here

The short version

Three first-party options, and only one of them is a route.

The decision people think they are making is which bottom sheet library to install. The decision that actually matters is whether your sheet is a screen or a component. Expo Router's formSheet presentation is a screen: you declare it in a layout file and navigate to it, and the router owns when it is open. Expo UI's BottomSheet is a component: you keep a boolean in state and pass the button that opens it as an anchor. React Native's core Modal is also a component, and the cheapest of the three, but it has no height control whatsoever.

Get that choice right and the rest is a handful of options. Get it wrong and you spend a week fighting a router over a filter panel. A sheet that is a step in a flow, such as a compose screen or a confirmation, belongs in the router. A sheet that modifies what is behind it, such as a sort order over a list or a filter over a map, usually does not.

What does Apple document about sheets and detents?

A class called UISheetPresentationController, available since iOS 15.0 and iPadOS 15.0, and documented as a presentation controller that manages the appearance and behavior of a sheet. The sizing vocabulary is one word. Apple's reference says sheet presentation controllers specify a sheet's size based on a detent, a height where a sheet naturally rests, and that detents allow a sheet to resize from one edge of its fully expanded frame while the other three edges remain fixed. That sentence is worth reading twice, because it tells you a sheet grows upward and keeps its other three edges where they are, which is why a sheet is a poor container for anything that needs to move sideways.

There are two system detents and one escape hatch. large is documented as a system detent for a sheet at full height. medium is documented as approximately half the height of the screen, and is inactive in compact height. Anything else you build yourself with the custom identifier and resolver method, which computes a height from a context Apple hands you. Apple's design guidance adds the part the API reference does not: sheets automatically support the large detent, adding the medium detent allows the sheet to rest at both heights, and specifying only medium prevents the sheet from expanding to full height. That page carries its own change log, most recently dated 24 March 2026.

The rest of the class is the behaviour you will end up wanting. prefersGrabberVisible controls whether the sheet shows a grabber at the top, which Apple's guidance describes as a small horizontal indicator that both shows people the sheet can be dragged and works with VoiceOver so they can resize it without seeing the screen. largestUndimmedDetentIdentifier sets the largest detent that does not dim the view underneath. prefersScrollingExpandsWhenScrolledToEdge decides whether scrolling the content pulls the sheet up to a larger detent. preferredCornerRadius does what it says. On the SwiftUI side the same feature is a view modifier, presentationDetents, available since iOS 16.0.

One more thing Apple says plainly, because it saves an argument later: display only one sheet at a time from the main interface, and if something people do inside a sheet results in another sheet appearing, close the first before showing the second. And support swiping to dismiss, because people expect to swipe vertically rather than hunt for a close button. Hold on to that second one. It comes back in a minute as a default that is set the other way.

Apple, UIKit reference for UISheetPresentationController, read 23 October 2026

What do React Native and Expo give you for the same thing?

Start with core React Native, because it is the honest baseline. The Modal component takes an iOS prop called presentationStyle with four values: fullScreen, pageSheet, formSheet and overFullScreen. Set it to pageSheet and you get a sheet. Now read the rest of the prop list, which fits in one breath: animationType, backdropColor, hardwareAccelerated, navigationBarTranslucent, onDismiss, onOrientationChange, allowSwipeDismissal, onRequestClose, onShow, presentationStyle, statusBarTranslucent, supportedOrientations, transparent and visible. There is no detent prop, no height prop and no grabber prop. Core Modal gives you the shape of a sheet and none of the sizing. The documentation also notes that supportedOrientations is ignored on iOS when the presentation style is pageSheet or formSheet. Read from the React Native 0.87 documentation, which prints its own last-updated date of 12 August 2026.

Expo Router is where the detents live. Set the presentation option on a screen to formSheet, which Expo describes as presenting a modal as a bottom sheet that app users can drag between different heights, called detents. Then sheetAllowedDetents takes either an array of numbers, which are fractions of the screen height between 0 and 1 and must be sorted in ascending order, or the string fitToContents, which sizes the sheet to its content. sheetInitialDetentIndex picks which one it opens at and defaults to 0. sheetGrabberVisible, sheetCornerRadius and sheetLargestUndimmedDetentIndex do the rest. There is also an Android-only experimental option, unstable_sheetFooter, for a footer that stays visible at every detent.

The third option is Expo UI, the package that brings SwiftUI and Jetpack Compose components into React. Its SwiftUI BottomSheet takes the control that opens it as an anchor prop, holds its open state in a boolean you own, and sizes itself through a presentationDetents modifier that accepts medium, large, a fraction of the screen height, or a fixed height in points. Expo describes the component as matching the official SwiftUI sheet API. It costs you one install and Expo lists it as included in Expo Go. Read from the version 58 reference. A sheet that holds a camera or a scanner is usually this shape rather than a route, which is also the pattern behind add document scanner.

Expo, Expo Router modals and form sheet presentation, page modification date 7 July 2026, read 23 October 2026

The iOS sheet surface, and the part you are handed

The left column is every sizing and behaviour member Apple lists on UISheetPresentationController, with Apple's description of what it does. The middle column is the Expo Router option whose documented behaviour matches it. Expo does not publish this mapping, so read the middle column as the option you set and the left column as the iOS behaviour you get, rather than as an API contract Expo has promised to keep.

What iOS hasWhat the Expo Router option isExposed
detentsThe array of heights where a sheet can rest.sheetAllowedDetents, as fractions of screen height in ascending order, or fitToContentsYes
selectedDetentIdentifierThe identifier of the most recently selected detent.sheetInitialDetentIndex, a number or last, defaulting to 0Yes
prefersGrabberVisibleWhether the sheet shows a grabber at the top.sheetGrabberVisibleYes, marked iOS only
preferredCornerRadiusThe corner radius that the sheet attempts to present with.sheetCornerRadius, in pixelsYes
largestUndimmedDetentIdentifierThe largest detent that does not dim the view underneath the sheet.sheetLargestUndimmedDetentIndex, a number, none or lastYes
prefersScrollingExpandsWhenScrolledToEdgeWhether scrolling expands the sheet to a larger detent.Nothing in the documented option listNo
prefersEdgeAttachedInCompactHeightWhether the sheet attaches to the bottom edge of the screen in a compact-height size class.Nothing in the documented option listNo
sourceViewThe view that the sheet centers itself over.Nothing in the documented option listNo
invalidateDetents()Notifies the sheet to re-evaluate its detent value in the next layout pass.Nothing in the documented option listNo

Is there a native option that needs no library?

Yes. Two of them, and the better one is the one almost nobody mentions. In a project that already uses Expo Router, the formSheet presentation and its five sheet options are part of Expo Router, so there is no package to add and no native module to configure. You edit a layout file. That is the full extent of the work for a sheet that drags between a quarter, a half and full height, with a grabber and an undimmed background behind the lowest detent.

The second option is core Modal with a pageSheet presentation style, which ships inside React Native itself. It is worth knowing about for two reasons. Some projects are not Expo Router projects. And some sheets do not need a second height at all, in which case the whole feature is two props and no dependency. The limit is absolute rather than awkward: nothing in the documented prop list sets a height, so if you need a half-open resting position, core Modal is not the tool.

So the usual advice, which is to install a bottom sheet package before you have looked, costs you a dependency for behaviour the platform already exposes. What a package buys you is the component shape rather than the detents, and Expo UI is the first-party version of that shape. The gesture physics on Android are the one place a third-party package still has a real argument, because Expo states that Android form sheets use the platform's own bottom sheet behaviour and cap out at three detents.

Check yours

Which of the three you should reach for

Tick what your sheet has to do. Every note below comes from Apple's UIKit reference or from Expo's own documentation, read on 23 October 2026. Tick two or three and you will usually find one option left standing.

Nothing ticked. If your sheet only has to appear, hold one screenful of content and go away again, core Modal with a pageSheet presentation style is the whole job and costs you no dependency.

Expo, sheet detent options and Android limitations, read 23 October 2026

What goes wrong when you add a bottom sheet to an app?

Five things, and four of them are in the documentation before you hit them. The first is landscape. Apple describes the medium system detent as inactive in compact height, so a sheet that offers only a half height has nowhere to rest the moment someone turns the phone. Apple's guidance says sheets automatically support large and that specifying only medium prevents the sheet from expanding to full height, so offer both and move on.

The second is swipe dismissal on core Modal. The prop is called allowSwipeDismissal, it defaults to false, and the documentation says you have to implement onRequestClose to handle the dismissal yourself. Apple's guidance says the opposite is what people expect, that they will swipe vertically rather than tap a button. A sheet nobody can flick away reads as broken, and the cause is a default rather than a bug.

The third is Android. Expo states that Android supports a maximum of three detents while iOS accepts any number, that the grabber option is iOS only, and that native stack headers and nested stack navigators are not supported inside form sheet screens, so headerShown, title and header buttons will not render inside the sheet. Put a title and your actions in the sheet content instead. The fourth is content sizing: Expo documents that flex: 1 works correctly on iOS with custom numeric detents from SDK 55 onward, and does not work with fitToContents, where you have to size the content explicitly because the sheet needs to know its actual height.

The fifth is the one no document will warn you about, because it is an architecture decision rather than an API. If your sheet is a route and the screen behind it holds state the sheet changes, you will end up passing values through navigation parameters and reading them back out, which is slow and fragile. Decide early. A sheet that works with no connection has the same shape of problem, which is why add offline mode is worth reading before you design the data flow rather than after.

Newly is an AI app builder for people who would rather describe a screen than configure one. You write what you want in plain English and it produces a real React Native and Expo project you own, with two-way GitHub sync so the sheet options above are code you can read and change. It uploads iOS builds to TestFlight, publishes to Google Play internal testing and builds a standalone Android APK. It is $25 a month and there is no free tier. It will not press submit on the App Store for you, because Apple requires the owner of the content to submit, and the steps for doing that yourself are in publish to app store.

Apple, system detents and compact height behaviour, read 23 October 2026

Questions people ask about adding a bottom sheet

No, and two separate first-party options prove it. React Native's own Modal component takes a presentationStyle of pageSheet or formSheet, which is a real iOS sheet with nothing installed, although its documented prop list contains no way to set a height. Expo Router exposes a presentation option of formSheet together with sheetAllowedDetents, sheetInitialDetentIndex, sheetGrabberVisible, sheetCornerRadius and sheetLargestUndimmedDetentIndex, which is the full detent behaviour and is part of Expo Router itself. Checked against both sets of documentation on 23 October 2026.

Write down the heights, then build the screen.

Two decisions and you are done. Is this sheet a step in a flow or a panel over the screen behind it. And what are the heights it rests at, in fractions, in ascending order. Everything else on this page is an option name.

Start building