RORK LABJP
FUNDING — Rork raised a $15M seed led by Left Lane Capital, with Peak XV, True Ventures, Goodwater, and a16z Speedrun joiningENGINE — Rork Max runs on Claude Code and Claude Opus 4.6; it drew 8M+ views on X and doubled annual revenue in two weeksSWIFT — Rork Max is the first web-based Swift app builder, positioned to replace Apple's traditional XcodePRODUCT — Rork Max covers the whole Apple ecosystem: iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and iMessageCLASSIC — The original Rork uses React Native (Expo), building iOS/Android apps from a plain-English descriptionPRICING — Start free; paid plans begin at $25/mo, and Rork Max is $200/moFUNDING — Rork raised a $15M seed led by Left Lane Capital, with Peak XV, True Ventures, Goodwater, and a16z Speedrun joiningENGINE — Rork Max runs on Claude Code and Claude Opus 4.6; it drew 8M+ views on X and doubled annual revenue in two weeksSWIFT — Rork Max is the first web-based Swift app builder, positioned to replace Apple's traditional XcodePRODUCT — Rork Max covers the whole Apple ecosystem: iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and iMessageCLASSIC — The original Rork uses React Native (Expo), building iOS/Android apps from a plain-English descriptionPRICING — Start free; paid plans begin at $25/mo, and Rork Max is $200/mo
Articles/Dev Tools
Dev Tools/2026-04-29Intermediate

Adding Bottom Sheets to a Rork App — A Practical Guide to @gorhom/bottom-sheet on iOS and Android

Rork's default Modal works for confirmations, but the moment you need multiple snap points or inertial scroll inside the sheet it falls short. This guide walks through dropping @gorhom/bottom-sheet into a Rork project, handling the keyboard, and smoothing out iOS/Android differences.

Rork444Bottom Sheet@gorhom/bottom-sheetReact Native180UI9Reanimated9Gesture Handler2

You don't usually need a custom bottom sheet in a Rork-generated app — until suddenly you do. A map screen with a sliding place card, a filter panel that peeks above a product list, a half-height form that you don't want to hide the screen behind: these are moments where a plain Modal isn't enough, and the gap between "I just want a sheet" and "I have a sheet that feels native on both platforms" turns out to be surprisingly wide.

Personally, I lost half a day trying to make Rork's Modal output behave like a two-step sheet, only to find that inertial scroll inside the sheet wasn't kicking in and the Android backdrop was rendering as solid black. The cleanest answer ended up being to stop fighting it and drop in @gorhom/bottom-sheet as a layered library. This guide is the version of the notes I wish I had on day one.

Why Rork's default Modal eventually runs out of room

Rork tends to emit React Native's standard Modal for sheet-ish UI. That's perfectly fine for a confirmation dialog or a small form. The friction starts when you want any of the following:

  • Multiple snap points (collapsed, half, full)
  • A scrollable list inside the sheet with proper inertia
  • Drag-to-resize gestures that work alongside the inner scroll
  • A backdrop fade and rounded corners that match iOS conventions

You can technically build all of this on top of Modal with PanResponder and a hand-rolled Animated.Value, but the maintenance cost adds up fast — separating gesture priority between the outer drag and the inner list is especially painful. Pulling in a focused library is the more honest tradeoff.

Adding @gorhom/bottom-sheet to a Rork project

Rork projects use Expo + React Native under the hood. @gorhom/bottom-sheet depends on Reanimated and Gesture Handler, so the cleanest approach is to confirm those are installed before pulling in the sheet itself.

# Run from the root of your Rork project
npx expo install react-native-reanimated react-native-gesture-handler
npm install @gorhom/bottom-sheet

Reanimated requires a Babel plugin, and the order matters — it has to be the last entry in the plugins array, or you'll get cryptic build errors that are hard to attribute back to the right cause.

// babel.config.js
module.exports = function (api) {
  api.cache(true);
  return {
    presets: ['babel-preset-expo'],
    plugins: [
      // ...other plugins...
      'react-native-reanimated/plugin', // must be last
    ],
  };
};

Gesture Handler needs a GestureHandlerRootView wrapping the entire app. In a Rork project that usually means adding a single line to the generated _layout.tsx.

// app/_layout.tsx
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { Slot } from 'expo-router';
 
export default function RootLayout() {
  return (
    <GestureHandlerRootView style={{ flex: 1 }}>
      <Slot />
    </GestureHandlerRootView>
  );
}

That's the entire setup. We've barely touched what Rork generated.

The smallest sheet that actually does something

Let's start with a sheet that has two snap points — half open and fully open — and a button that triggers it. Holding a useRef to the sheet so you can call expand() or close() from anywhere makes it easy to plug into screens generated later by the AI.

import React, { useCallback, useMemo, useRef } from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';
import BottomSheet, { BottomSheetView } from '@gorhom/bottom-sheet';
 
export default function PlaceDetailScreen() {
  const sheetRef = useRef<BottomSheet>(null);
 
  // Use percentages so the layout adapts across device sizes
  const snapPoints = useMemo(() => ['25%', '60%', '90%'], []);
 
  const openSheet = useCallback(() => {
    sheetRef.current?.snapToIndex(1); // open at the 60% position
  }, []);
 
  return (
    <View style={styles.container}>
      <Button title="Show details" onPress={openSheet} />
 
      <BottomSheet
        ref={sheetRef}
        index={-1} // start closed
        snapPoints={snapPoints}
        enablePanDownToClose
        backgroundStyle={styles.sheetBg}
        handleIndicatorStyle={styles.indicator}
      >
        <BottomSheetView style={styles.sheetBody}>
          <Text style={styles.title}>Cafe Hirokawa</Text>
          <Text>Hours: 10:00 - 19:00</Text>
          <Text>Shibuya, Tokyo</Text>
        </BottomSheetView>
      </BottomSheet>
    </View>
  );
}
 
const styles = StyleSheet.create({
  container: { flex: 1, padding: 24 },
  sheetBg: { backgroundColor: '#ffffff' },
  indicator: { backgroundColor: '#cccccc', width: 48 },
  sheetBody: { padding: 20 },
  title: { fontSize: 20, fontWeight: '600', marginBottom: 8 },
});

Tapping the button slides the sheet up to 60%; the user can drag it to 90%, back down to 25%, or off-screen entirely. Without enablePanDownToClose, the dismiss gesture quietly stops working — that's an easy one to forget when you're iterating.

Designing the snap points

Snap points read better as percentages than as pixel heights. iPhone SE and iPad need to feel right with the same configuration, and percentages adapt without you having to reach for a Dimensions.get() calculation. If your sheet contains a fixed-height card, you can also pass 'CONTENT_HEIGHT' (v4+) and let the library size itself.

Adding more snap points is tempting, but I've found three is usually the ceiling — anything more and users start to feel like the sheet is "fighting them" mid-drag. The exception is map-style apps where Apple Maps' minimal handle position (around 10%) makes sense as a fourth stop.

If you need to react to the user's position — focus an input when the sheet is fully open, for example — onChange gives you the index. Just remember any side effect that touches React state has to be wrapped in runOnJS, since the gesture handlers run on Reanimated's worklet thread. I dug into that pattern in more depth in Advanced animations in Rork with Reanimated and Gesture Handler.

Forms inside a sheet, and the keyboard problem

The first time you put a TextInput in a bottom sheet, the keyboard will hide the field. @gorhom/bottom-sheet ships a BottomSheetTextInput specifically for this — it tracks focus and adjusts the sheet's offset automatically.

import BottomSheet, {
  BottomSheetView,
  BottomSheetTextInput,
} from '@gorhom/bottom-sheet';
 
<BottomSheet
  ref={sheetRef}
  index={-1}
  snapPoints={['40%', '90%']}
  keyboardBehavior="interactive" // iOS: track keyboard naturally
  keyboardBlurBehavior="restore" // restore on dismiss
  android_keyboardInputMode="adjustResize"
>
  <BottomSheetView style={{ padding: 16 }}>
    <BottomSheetTextInput
      placeholder="Add a note..."
      style={{
        height: 44,
        paddingHorizontal: 12,
        backgroundColor: '#f4f4f4',
        borderRadius: 8,
      }}
    />
  </BottomSheetView>
</BottomSheet>

keyboardBehavior="interactive" is iOS-only and makes the sheet follow the keyboard smoothly. On Android you also have to set softwareKeyboardLayoutMode: "resize" in app.json; leave it on "pan" and the entire sheet will get pushed up out of position when the keyboard opens.

If you have multiple inputs and notice a beat of lag when switching focus, swapping the inner container for BottomSheetScrollView usually smooths it out — and you get correct scroll-inside-sheet behavior as a bonus.

Where iOS and Android quietly disagree

Once you start testing on real devices, the OS-level differences show up faster than you'd expect. Three I've personally tripped on:

  • Backdrop dimming. iOS fades the backdrop nicely on its own. Some Android devices render a solid black instead of a translucent overlay unless you pass BottomSheetBackdrop explicitly. Worth wiring up from day one.
  • Handle hit area. Android's default handle target is small, and fingers slip off. Bumping handleHeight to ~28-32 makes the drag feel intentional.
  • Hardware back button. On Android the user expects the back button to close just the sheet, not the screen. Register a BackHandler inside useFocusEffect, call sheetRef.current?.close(), and return true to consume the event.

None of these are things Rork's default output considers, so a quick pass on a mid-tier Android right after dropping in the sheet is well worth the time. If your sheet starts crashing only on device, How to debug a Rork app that crashes on a real device covers the diagnostic flow I usually run.

For a more polished feel, swap out the inner spinner for a skeleton during loading. The pattern I use is in Implementing skeleton screens in Rork for smoother loading, and it pairs naturally with a sheet that's already animating in.

Start small and add as you go

If Rork's default Modal is doing the job, leave it alone. The moment you reach for a half-open sheet or a list inside a draggable card, though, the cost-benefit shifts hard toward a dedicated library — and @gorhom/bottom-sheet slots into a Rork project without forcing a rewrite of what the AI generated.

A good first migration is to pick one screen where the win is obvious — a category picker that slides up from the bottom, a place detail card on a map — and replace just that one. Once you've felt how the gestures behave on real hardware, deciding where else to use it gets a lot easier.

Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

Dev Tools2026-03-30
Rork × React Native Reanimated & Gesture Handler — Building 60fps Animations and Advanced Gesture Interactions
Keeping Reanimated 3 and Gesture Handler at 60fps, told through the production pitfalls I hit running wallpaper and calming-tone apps: delayed runOnJS, useDerivedValue caching, FlashList pairing, and missing cancelAnimation — each with real measurements.
Dev Tools2026-06-21
Add Long-Press Drag Reordering to Your Rork Favorites List Without the Jank
A practical walkthrough for retrofitting long-press drag reordering onto a Rork-generated favorites list: keeping re-renders down, respecting the worklet boundary, persisting the order, and avoiding ghost cards and scroll conflicts.
Dev Tools2026-04-26
Why Your Rork Modal Won't Close — 5 Common Pitfalls and How to Fix Them
The close button doesn't fire, tapping the backdrop does nothing, and buttons inside the modal feel dead. In Rork-generated React Native code, eight times out of ten the cause is one of these five patterns.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →