Controls
<Dialog>
<Button>Open Sheet</Button>
<Sheet>
<DialogContent>
<DialogHeader>
<DialogTitle>Sheet Title</DialogTitle>
<DialogDescription>This is a sheet description.</DialogDescription>
</DialogHeader>
<DialogBody>
<p>Sheet content goes here.</p>
</DialogBody>
<DialogFooter>
<Button slot="close">Close</Button>
</DialogFooter>
</DialogContent>
</Sheet>
</Dialog>Installation
pnpm dlx shadcn@latest add @dotui/sheetUsage
import { Button } from "@/components/ui/button"
import { Dialog, DialogContent } from "@/components/ui/dialog"
import { Sheet } from "@/components/ui/sheet"<Dialog>
<Button>Open Sheet</Button>
<Sheet>
<DialogContent>Sheet content</DialogContent>
</Sheet>
</Dialog>Anatomy
A Sheet has no built-in trigger. Wrap it in a Dialog with a Button child as the trigger, and put your content inside DialogContent. A sheet can also hold a menu or a listbox directly — that's how pickers render on mobile.
import { Button } from "@/components/ui/button"
import { Dialog, DialogContent } from "@/components/ui/dialog"
import { Sheet, SheetHandle } from "@/components/ui/sheet"<Dialog>
<Button />
<Sheet>
<DialogContent>
<SheetHandle />
</DialogContent>
</Sheet>
</Dialog>SheetHandle is a visible swipe affordance. The whole sheet swipes: it rides the browser's native scrolling, so it follows the finger with momentum and rubber-banding for free.
Position
position anchors the sheet to an edge: bottom (the default), top, start, end, or physical left and right. start and end mirror in right-to-left locales. swipeDirection defaults to the same edge.
<Sheet position="end">
<DialogContent>...</DialogContent>
</Sheet>Snap points
snapPoints stops the sheet partway open. Each value is how much of the sheet shows, in pixels or as a CSS length — percentages are of the sheet's size. It opens at the first one and drags up to its full height.
<Sheet snapPoints={[240]}>
<DialogContent>...</DialogContent>
</Sheet>Open state
Rendered under a Dialog, the sheet shares the dialog's open state, so the trigger Button opens it with no wiring. To drive it yourself, pass isOpen with onOpenChange (or defaultOpen for uncontrolled).
const [isOpen, setIsOpen] = React.useState(false)
<Sheet isOpen={isOpen} onOpenChange={setIsOpen}>
<DialogContent>...</DialogContent>
</Sheet>Dismissal
The sheet dismisses on swipe, outside interaction, and Escape. preventDismissal blocks all three; with snap points, the sheet still swipes between them.
<Sheet preventDismissal>
<DialogContent>...</DialogContent>
</Sheet>Examples
Basic Sheet
Settings Panel
Place Details
Nested Sheets
Scrollable Content
Form Sheet
API Reference
Sheet
A sheet is a swipeable overlay that slides in from an edge of the viewport.
| Prop | Type | Default | |
|---|---|---|---|
ReactNode | — | ||
boolean | — | ||
boolean | — | ||
Element | document.body | ||
union | 'bottom' | ||
boolean | false | ||
function | — | ||
(string | number)[] | — | ||
boolean | — | ||
union | — | ||
SheetHandle
Last updated on 10/20/2026