A filled panel that slides in from an edge. Its free corners are rounded, and it closes from the button, Escape key, or scrim.
tsx
import { Button } from "@/components/ui/button"import { Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger,} from "@/components/ui/sheet"export function Example() { return ( <Sheet> <SheetTrigger asChild> <Button variant="outline">Open</Button> </SheetTrigger> <SheetContent> <SheetHeader> <SheetTitle>Edit profile</SheetTitle> <SheetDescription> Make changes to your profile here. Click save when you're done. </SheetDescription> </SheetHeader> <div className="grid gap-4 px-4"> <div className="grid gap-1.5"> <label htmlFor="sheet-name" className="text-sm tracking-wide"> Name </label> <input id="sheet-name" defaultValue="Carol Williams" className="h-9 w-full rounded-md border border-border bg-transparent px-3 text-sm tracking-wide" /> </div> <div className="grid gap-1.5"> <label htmlFor="sheet-username" className="text-sm tracking-wide"> Username </label> <input id="sheet-username" defaultValue="@peduarte" className="h-9 w-full rounded-md border border-border bg-transparent px-3 text-sm tracking-wide" /> </div> </div> <SheetFooter> <Button type="submit" className="transition-colors"> Save changes </Button> <SheetClose asChild> <Button variant="outline" className="transition-colors"> Close </Button> </SheetClose> </SheetFooter> </SheetContent> </Sheet> )}Installation
npx @arctis-sh/@arctis-sh/ui@latest add sheetUsage
import { Button } from "@/components/ui/button"import { Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger,} from "@/components/ui/sheet"<Sheet> <SheetTrigger asChild> <Button variant="outline">Open</Button> </SheetTrigger> <SheetContent> <SheetHeader> <SheetTitle>Edit profile</SheetTitle> <SheetDescription> Update your details, then save. </SheetDescription> </SheetHeader> <SheetFooter> <Button type="submit">Save changes</Button> <SheetClose asChild> <Button variant="outline">Close</Button> </SheetClose> </SheetFooter> </SheetContent></Sheet>Composition
Sheet
├── SheetTrigger
└── SheetContent
├── SheetHeader
│ ├── SheetTitle
│ └── SheetDescription
├── SheetFooter
│ └── SheetClose
└── (close button)Basic
An edit-profile sheet with fields above and actions in the footer.
tsx
import { Button } from "@/components/ui/button"import { Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger,} from "@/components/ui/sheet"export function Example() { return ( <Sheet> <SheetTrigger asChild> <Button variant="outline">Open</Button> </SheetTrigger> <SheetContent> <SheetHeader> <SheetTitle>Edit profile</SheetTitle> <SheetDescription> Make changes to your profile here. Click save when you're done. </SheetDescription> </SheetHeader> <div className="grid gap-4 px-4"> <div className="grid gap-1.5"> <label htmlFor="sheet-name" className="text-sm tracking-wide"> Name </label> <input id="sheet-name" defaultValue="Carol Williams" className="h-9 w-full rounded-md border border-border bg-transparent px-3 text-sm tracking-wide" /> </div> <div className="grid gap-1.5"> <label htmlFor="sheet-username" className="text-sm tracking-wide"> Username </label> <input id="sheet-username" defaultValue="@peduarte" className="h-9 w-full rounded-md border border-border bg-transparent px-3 text-sm tracking-wide" /> </div> </div> <SheetFooter> <Button type="submit" className="transition-colors"> Save changes </Button> <SheetClose asChild> <Button variant="outline" className="transition-colors"> Close </Button> </SheetClose> </SheetFooter> </SheetContent> </Sheet> )}Side
Set side on SheetContent to top, right, bottom, or left. Each example keeps the content simple and includes footer actions.
tsx
import { Button } from "@/components/ui/button"import { Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger,} from "@/components/ui/sheet"const SIDES = ["top", "right", "bottom", "left"] as constconst sideCopy = { top: { title: "What’s new this week", description: "A short tour of the updates that landed since your last visit — nothing you have to act on right away.", body: [ "We’ve shipped a quieter notifications panel, faster search across your workspace, and clearer activity history so you can pick up where you left off without digging through old threads.", "Shared links now open with the right permission prompt, and comment mentions include a bit more context so you know why someone pulled you in.", "If something looks off after the update, you can roll individual features back from Settings → Experiments while we keep polishing the defaults.", ], }, right: { title: "Project details", description: "Context that stays beside your canvas so you don’t lose place jumping between tabs.", body: [ "Status, owners, and recent updates stay in view while you edit. Use this panel when you need a quick check without leaving the document you’re already reading.", "Timeline events are sorted newest first. Expand any row for the full note, attachments, and who made the change — useful when you’re catching up after time away.", "You can pin this sheet open while you work, or close it anytime. Nothing here edits the file until you choose an action at the bottom.", ], }, bottom: { title: "Confirm before you leave", description: "You still have unsaved edits on this page. Take a second before discarding them.", body: [ "Closing now will drop the draft you’ve been working on, including comments that haven’t been posted yet and any fields you changed in the last few minutes.", "If you meant to keep going, stay on this page and finish reviewing. When you’re ready, save from the main toolbar — this sheet is only a warning, not a commit.", "Sure you want to leave? Confirm below and we’ll return you to the previous view without storing this draft.", ], }, left: { title: "Browse the library", description: "Collections, filters, and saved views — without covering the canvas you’re editing.", body: [ "Pick a section here, then keep working on the right. The library stays in one continuous flow so you can jump between references without losing selection in the main editor.", "Starred items float to the top of each collection. Filters remember the last combination you used in this workspace, so opening the panel again should feel familiar.", "Need something new? Create a collection from the footer, or close this sheet and search from the command menu if you already know the name.", ], },} as constexport function Example() { return ( <div className="flex flex-wrap gap-2"> {SIDES.map((side) => { const copy = sideCopy[side] return ( <Sheet key={side}> <SheetTrigger asChild> <Button variant="outline" className="capitalize"> {side} </Button> </SheetTrigger> <SheetContent side={side}> <SheetHeader> <SheetTitle>{copy.title}</SheetTitle> <SheetDescription>{copy.description}</SheetDescription> </SheetHeader> <div className="grid gap-3 overflow-y-auto px-4 text-sm tracking-wide text-muted-foreground"> {copy.body.map((paragraph) => ( <p key={paragraph.slice(0, 24)}>{paragraph}</p> ))} </div> <SheetFooter> <Button type="button" className="transition-colors"> Continue </Button> <SheetClose asChild> <Button variant="outline" className="transition-colors"> Close </Button> </SheetClose> </SheetFooter> </SheetContent> </Sheet> ) })} </div> )}No Close Button
Pass showCloseButton={false} to hide the corner close control. The sheet still closes with Escape or the scrim.
tsx
import { Button } from "@/components/ui/button"import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger,} from "@/components/ui/sheet"export function Example() { return ( <Sheet> <SheetTrigger asChild> <Button variant="outline">Open Sheet</Button> </SheetTrigger> <SheetContent showCloseButton={false}> <SheetHeader> <SheetTitle>No close button</SheetTitle> <SheetDescription> This sheet hides the default close control. Dismiss with Escape or by clicking the overlay. </SheetDescription> </SheetHeader> </SheetContent> </Sheet> )}API Reference
Sheet
| Prop | Type | Default |
|---|---|---|
| open | boolean | — |
| defaultOpen | boolean | false |
| onOpenChange | (open: boolean) => void | — |
<Sheet> <SheetTrigger asChild> <Button variant="outline">Open</Button> </SheetTrigger> <SheetContent> <SheetHeader> <SheetTitle>Title</SheetTitle> <SheetDescription>Description</SheetDescription> </SheetHeader> </SheetContent></Sheet>SheetTrigger
| Prop | Type | Default |
|---|---|---|
| asChild | boolean | false |
| className | string | — |
<SheetTrigger asChild> <Button variant="outline">Open</Button></SheetTrigger>SheetClose
| Prop | Type | Default |
|---|---|---|
| asChild | boolean | false |
| className | string | — |
<SheetClose asChild> <Button variant="outline">Close</Button></SheetClose>SheetContent
Filled panel. Radius on the free edge only (opposite side).
| Prop | Type | Default |
|---|---|---|
| side | "top" | "right" | "bottom" | "left" | "right" |
| showCloseButton | boolean | true |
| className | string | — |
<SheetContent side="right"> Content</SheetContent>SheetHeader
| Prop | Type | Default |
|---|---|---|
| className | string | — |
<SheetHeader> <SheetTitle>Title</SheetTitle> <SheetDescription>Description</SheetDescription></SheetHeader>SheetFooter
Pinned to the bottom of tall side sheets via mt-auto.
| Prop | Type | Default |
|---|---|---|
| className | string | — |
<SheetFooter> <Button>Save</Button></SheetFooter>SheetTitle
| Prop | Type | Default |
|---|---|---|
| className | string | — |
<SheetTitle>Title</SheetTitle>SheetDescription
| Prop | Type | Default |
|---|---|---|
| className | string | — |
<SheetDescription>Description</SheetDescription>