Bottom sheet

A sheet that rests at a peek or full height and follows your finger.

overlaygesturemotion
Preview
Bottom sheet preview

When to use

  • Mobile-first secondary tasks such as details, filters, or share options.
  • Content that benefits from a peek height before expanding to nearly full screen.
  • Maps and media views where the sheet should be dragged between detents.

When not to use

  • Use dialog for interrupting decisions on any screen size.
  • Use drawer for side panels on wide desktop layouts.
  • Use popover for small anchored content that should not dim the page.

shadcn CLI

Also installs (1)

  • manicat-foundation
Manual install(+)
registry.jsonjson
{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "bottom-sheet",
  "type": "registry:ui",
  "title": "Bottom sheet",
  "description": "A sheet that rests at a peek or full height and follows your finger.",
  "categories": [
    "disclosure"
  ],
  "dependencies": [
    "@radix-ui/react-dialog",
    "lucide-react",
    "motion"
  ],
  "registryDependencies": [
    "https://manicat.viraza.net/r/manicat-foundation.json"
  ],
  "files": [
    {
      "path": "registry/components/bottom-sheet/bottom-sheet.tsx",
      "type": "registry:component",
      "target": "@components/manicat/bottom-sheet/bottom-sheet.tsx"
    },
    {
      "path": "registry/components/bottom-sheet/bottom-sheet.module.css",
      "type": "registry:component",
      "target": "@components/manicat/bottom-sheet/bottom-sheet.module.css"
    }
  ],
  "docs": "Docs and live preview: https://uiarc.dev/components/bottom-sheet",
  "meta": {
    "tier": "free",
    "kind": "component",
    "docs": "https://manicat.viraza.net/components/bottom-sheet",
    "markdown": "https://uiarc.dev/components/bottom-sheet/markdown",
    "tags": [
      "overlay",
      "gesture",
      "motion"
    ]
  }
}

Usage

Install the component, then copy this into your app. Change the content and the props to fit your screen.

example.tsxtsx
"use client";

import { BottomSheet, BottomSheetClose } from "@/components/manicat/bottom-sheet/bottom-sheet";
import { Button } from "@/components/manicat/button/button";

export function BottomSheetExample() {
  return (
    <BottomSheet
      title="Share this project"
      description="The sheet rises from the bottom edge on small screens."
      trigger={
        <Button variant="secondary" className="w-full">
          Open sheet
        </Button>
      }
    >
      <BottomSheetClose asChild>
        <Button className="w-full">Copy link</Button>
      </BottomSheetClose>
    </BottomSheet>
  );
}

Examples

Real screens rather than isolated props. Switch to Code to copy a whole example into your project.

Share sheet On small screens the sheet rises from the bottom edge.

Share sheet On small screens the sheet rises from the bottom edge. preview

Keyboard interactions

KeysAction
ArrowUp / ArrowDownOn the grabber, moves one detent up or down.
Home / EndOn the grabber, jumps to the tallest or smallest detent.
Enter / SpaceOn the grabber, toggles between smallest and tallest detent.
ArrowDown / PageDown / SpaceOn the sheet, expands to full height first, then scrolls the content.
EscapeCloses the sheet.

Accessibility

  • Built on Radix Dialog: role="dialog", focus trap, and focus return to the trigger. Initial focus lands on the sheet itself.
  • The grabber is a button with aria-expanded and an Expand or Collapse sheet label.
  • Detent changes are announced through a polite live region.
  • Tabbing into content below the fold expands the sheet so focused elements are visible.

Motion

  • One motion value drives the sheet: drags follow the finger 1:1, rubber-band past the tallest detent, and releases project velocity to pick a detent or dismiss.
  • The backdrop dim is a function of sheet position, so it tracks drags instead of running on a timer.
  • Reduced motion jumps between detents and fades the sheet in and out.

Responsive behavior

  • The sheet is min(100%, 36rem) wide and detents are fractions of the dynamic viewport height, so it adapts to mobile browser chrome.
  • Below 40rem the header and body use tighter side padding.
  • Touch drags on the content move the sheet until it is fully open, then the content scrolls; mouse and pen drag from the header.

Performance

  • One motion value drives position and backdrop, so drags do not re-render React per frame.
  • A ResizeObserver re-fits detents on viewport changes; content is not virtualized, so paginate long lists inside it.

Notes for AI

  • Use for mobile-first secondary tasks where a peek helps: details, filters, share options. Use dialog for interrupting decisions and drawer for side panels on wide layouts.
  • Pass a trigger or control open yourself. Use a single-value detents array for a fixed-height sheet.