React + MotionOverlays

Bottom sheet

A panel that slides up from the bottom with a grab handle, and closes when you drag it down or tap outside.

Settings

240px

Code

import { useState } from "react";
import { AnimatePresence, motion } from "framer-motion";

const spring = { type: "spring" as const, stiffness: 300, damping: 30 };

interface BottomSheetProps {
  height?: number;
}

export default function BottomSheet({ height = 240 }: BottomSheetProps) {
  const [open, setOpen] = useState(false);

  return (
    <div className="relative flex h-64 w-full flex-col items-center justify-center overflow-hidden rounded-2xl bg-black/5 text-[#1d1d1f] dark:text-[#f5f5f7] dark:bg-white/10 sm:h-72">
      {/* Relleno: da a la caja 320 px de ancho natural sin impedir que encoja en pantallas estrechas */}
      <svg aria-hidden className="h-0 w-80 max-w-full" />
      <button
        type="button"
        onClick={() => setOpen(true)}
        className="rounded-full bg-[#0071e3] px-5 py-2.5 text-sm font-medium text-white"
      >
        Open sheet
      </button>
      <AnimatePresence>
        {open && (
          <>
            <motion.button
              type="button"
              aria-label="Close"
              className="absolute inset-0 bg-[#1d1d1f]/10 dark:bg-[#f5f5f7]/10"
              onClick={() => setOpen(false)}
              initial={{ opacity: 0 }}
              animate={{ opacity: 1 }}
              exit={{ opacity: 0 }}
            />
            <motion.div
              drag="y"
              dragConstraints={{ top: 0, bottom: 100 }}
              onDragEnd={(_, info) => {
                if (info.offset.y > 60) setOpen(false);
              }}
              initial={{ y: "100%" }}
              animate={{ y: 0 }}
              exit={{ y: "100%" }}
              transition={spring}
              className="absolute inset-x-0 bottom-0 rounded-t-[28px] bg-white dark:bg-[#1c1c1e] p-5 shadow-[0_4px_12px_rgba(0,0,0,0.04),0_24px_60px_rgba(0,0,0,0.1)]"
              style={{ minHeight: `min(${height}px, 100%)` }}
            >
              <div className="mx-auto mb-5 h-1.5 w-10 rounded-full bg-black/5 dark:bg-white/10" />
              <h3 className="text-lg font-semibold">Your next getaway</h3>
              <p className="mt-1 text-sm text-[#86868b]">Save this stay for later.</p>
              <button
                type="button"
                className="mt-5 w-full rounded-full bg-[#0071e3] py-3 text-sm font-medium text-white"
              >
                Save
              </button>
            </motion.div>
          </>
        )}
      </AnimatePresence>
    </div>
  );
}

Prompt for AI

Paste it into ChatGPT, Claude or Cursor and it will adapt the block to your project with these settings, even if you don’t use React.

More in Overlays

Where to use it

On phones, a bottom sheet is kinder than a centered modal: it opens where the thumb already is and can be flicked away. Use it for “Save this place” on a travel app, picking a size in a shop or sharing a blog post. This React bottom sheet with drag to close behaves the way people expect from native apps.

Keep the content short enough to fit without scrolling inside the sheet; if it needs a long form, open a full page instead. Set the height so the page behind stays partly visible, which reminds people they can go back.