Bottom sheet
A sheet that rests at a peek or full height and follows your finger.
overlaygesturemotion
Preview
Bottom sheet preview
Live · keyboard ready
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.
Keyboard interactions
| Keys | Action |
|---|---|
| ArrowUp / ArrowDown | On the grabber, moves one detent up or down. |
| Home / End | On the grabber, jumps to the tallest or smallest detent. |
| Enter / Space | On the grabber, toggles between smallest and tallest detent. |
| ArrowDown / PageDown / Space | On the sheet, expands to full height first, then scrolls the content. |
| Escape | Closes 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.