4 min
Bottom sheets, from snap points to the keyboard inside them
A sheet is three gestures fighting over one finger. The drag, the scroll inside it, and the keyboard that wants the same space.
You tap a comment icon and a panel slides up from the bottom of the screen. Under your thumb, the bottom sheet has to decide what your drag means. Close me, expand me, or scroll the list inside me.
A bottom sheet is three gestures sharing one finger
The first version usually works. A view, a translateY, a pan handler. Then real content goes in, and the edges show.
Put a list inside the sheet. Now a downward drag is ambiguous. If the list is scrolled halfway, the drag should scroll it. If the list is at the top, the same drag should pull the sheet down. A plain ScrollView inside a pan gesture does not know which one you meant, so the sheet either jumps or refuses to move. The bottom sheet scroll handoff is that exact moment on video.
Put a text input inside the sheet. The keyboard opens and covers the bottom of the screen, which is exactly where the sheet lives. KeyboardAvoidingView adjusts the layout around it, but the sheet is also animating its own position. Two systems now move the same view, and the post on the buried submit button covers why they disagree.
Then there is height. A sheet that sits at a fixed 50% looks wrong with three rows of content and wrong again with thirty. Snap points fix the second case. Content-sized sheets fix the first. Most screens need both.
How bottom sheets are usually built in React Native
The most common answer is @gorhom/bottom-sheet. It runs on Reanimated and React Native Gesture Handler, so the drag follows the finger on the UI thread. It ships its own scrollables, BottomSheetScrollView and BottomSheetFlatList, because the scroll-versus-drag handoff is the hard part. Its BottomSheetTextInput and the keyboardBehavior prop exist for the keyboard case. enableDynamicSizing is on by default in v5, which sizes the sheet to its content unless you turn it off and pass snapPoints. The tradeoff is weight. You take on two native dependencies and a provider for one component.
The second answer is the platform. React Native's Modal accepts presentationStyle="pageSheet" on iOS, which gives you the system card that slides up and dismisses with a swipe. It costs nothing to add. It also stops at iOS, and you do not get custom snap points from it.
Expo UI goes further on the native side, and a bottom sheet for iOS built on it shows what you get when the OS draws the sheet instead of JavaScript.
The third answer is your own. A GestureDetector and a shared value for the offset, written for one screen. That is the right call when the sheet is a single fixed height with no scroll inside. It stops being the right call the first time someone adds a list.
Where they show up decides which one you need. A comment sheet under a video is a scroll problem. A share sheet is a height problem. A checkout sheet with a promo code field is a keyboard problem. A map with a results drawer pinned at three heights is a snap-point problem, and usually all of the others too.
The sheet is one member of a bigger family that also includes dialogs, menus, and popovers, and the overlays post explains why I keep them together.
The hard part is remembering which sheet moved well
Knowing the library is not the search. The search is for the one you saw that felt right. The handle that rubber-banded past the top. The backdrop that dimmed in step with the drag. The list that handed off to the sheet without a stutter. That detail lives in motion, and motion is hard to find by name.
Fill it with real content before you judge it
An empty sheet always feels good. Drop in a long list, a text field, and a short message, then drag each one to every snap point. The sheets to put through that are in the Bottom Sheet filter.