Sheet
A dialog that slides in from an edge of the screen and runs along it: details of a row, filters, settings, a form next to the page it changes. It shares Dialog's scrim, raised surface and close button; only the side that faces the content has a border and page corners.
Preview
import { Button } from "@/components/ui/button"
import { Field, FieldControl, FieldLabel } from "@/components/ui/field"
import { Input } from "@/components/ui/input"
import { Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet"
<Sheet>
<SheetTrigger asChild>
<Button variant="secondary">Edit project</Button>
</SheetTrigger>
<SheetContent>
<SheetHeader>
<SheetTitle>Edit project</SheetTitle>
<SheetDescription>Changes are visible to everyone on your team.</SheetDescription>
</SheetHeader>
<Field>
<FieldLabel>Name</FieldLabel>
<FieldControl>
<Input defaultValue="Werewolf" />
</FieldControl>
</Field>
<SheetFooter>
<SheetClose asChild>
<Button variant="secondary">Cancel</Button>
</SheetClose>
<Button>Save</Button>
</SheetFooter>
</SheetContent>
</Sheet>Usage
When to use
- Work next to the page: the details of a row, filters, a short form.
- Navigation on a narrow screen, from the start edge, or a few choices on a phone, from the bottom.
When not to use
- Confirming an action that destroys data. Use AlertDialog.
- A focused task that stands on its own, such as renaming a project. Use Dialog.
- A menu of actions. Use DropdownMenu.
Variants
side
Set on SheetContent. The edge it slides in from. right for details and forms next to a list; left for navigation; bottom for short choices on a phone; top rarely.
import { Button } from "@/components/ui/button"
import { Field, FieldControl, FieldLabel } from "@/components/ui/field"
import { Input } from "@/components/ui/input"
import { Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet"
<Sheet>
<SheetTrigger asChild>
<Button variant="secondary">Edit project</Button>
</SheetTrigger>
<SheetContent side="top">
<SheetHeader>
<SheetTitle>Edit project</SheetTitle>
<SheetDescription>Changes are visible to everyone on your team.</SheetDescription>
</SheetHeader>
<Field>
<FieldLabel>Name</FieldLabel>
<FieldControl>
<Input defaultValue="Werewolf" />
</FieldControl>
</Field>
<SheetFooter>
<SheetClose asChild>
<Button variant="secondary">Cancel</Button>
</SheetClose>
<Button>Save</Button>
</SheetFooter>
</SheetContent>
</Sheet>
<Sheet>
<SheetTrigger asChild>
<Button variant="secondary">Edit project</Button>
</SheetTrigger>
<SheetContent>
<SheetHeader>
<SheetTitle>Edit project</SheetTitle>
<SheetDescription>Changes are visible to everyone on your team.</SheetDescription>
</SheetHeader>
<Field>
<FieldLabel>Name</FieldLabel>
<FieldControl>
<Input defaultValue="Werewolf" />
</FieldControl>
</Field>
<SheetFooter>
<SheetClose asChild>
<Button variant="secondary">Cancel</Button>
</SheetClose>
<Button>Save</Button>
</SheetFooter>
</SheetContent>
</Sheet>
<Sheet>
<SheetTrigger asChild>
<Button variant="secondary">Edit project</Button>
</SheetTrigger>
<SheetContent side="bottom">
<SheetHeader>
<SheetTitle>Edit project</SheetTitle>
<SheetDescription>Changes are visible to everyone on your team.</SheetDescription>
</SheetHeader>
<Field>
<FieldLabel>Name</FieldLabel>
<FieldControl>
<Input defaultValue="Werewolf" />
</FieldControl>
</Field>
<SheetFooter>
<SheetClose asChild>
<Button variant="secondary">Cancel</Button>
</SheetClose>
<Button>Save</Button>
</SheetFooter>
</SheetContent>
</Sheet>
<Sheet>
<SheetTrigger asChild>
<Button variant="secondary">Edit project</Button>
</SheetTrigger>
<SheetContent side="left">
<SheetHeader>
<SheetTitle>Edit project</SheetTitle>
<SheetDescription>Changes are visible to everyone on your team.</SheetDescription>
</SheetHeader>
<Field>
<FieldLabel>Name</FieldLabel>
<FieldControl>
<Input defaultValue="Werewolf" />
</FieldControl>
</Field>
<SheetFooter>
<SheetClose asChild>
<Button variant="secondary">Cancel</Button>
</SheetClose>
<Button>Save</Button>
</SheetFooter>
</SheetContent>
</Sheet>size
Set on SheetContent. Widest it gets on the left and right (sm 320px, md 448px, lg 672px), tallest on the top and bottom (a third, half, three quarters of the screen). full covers the screen along that side, with no corners.
import { Button } from "@/components/ui/button"
import { Field, FieldControl, FieldLabel } from "@/components/ui/field"
import { Input } from "@/components/ui/input"
import { Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet"
<Sheet>
<SheetTrigger asChild>
<Button variant="secondary">Edit project</Button>
</SheetTrigger>
<SheetContent size="sm">
<SheetHeader>
<SheetTitle>Edit project</SheetTitle>
<SheetDescription>Changes are visible to everyone on your team.</SheetDescription>
</SheetHeader>
<Field>
<FieldLabel>Name</FieldLabel>
<FieldControl>
<Input defaultValue="Werewolf" />
</FieldControl>
</Field>
<SheetFooter>
<SheetClose asChild>
<Button variant="secondary">Cancel</Button>
</SheetClose>
<Button>Save</Button>
</SheetFooter>
</SheetContent>
</Sheet>
<Sheet>
<SheetTrigger asChild>
<Button variant="secondary">Edit project</Button>
</SheetTrigger>
<SheetContent>
<SheetHeader>
<SheetTitle>Edit project</SheetTitle>
<SheetDescription>Changes are visible to everyone on your team.</SheetDescription>
</SheetHeader>
<Field>
<FieldLabel>Name</FieldLabel>
<FieldControl>
<Input defaultValue="Werewolf" />
</FieldControl>
</Field>
<SheetFooter>
<SheetClose asChild>
<Button variant="secondary">Cancel</Button>
</SheetClose>
<Button>Save</Button>
</SheetFooter>
</SheetContent>
</Sheet>
<Sheet>
<SheetTrigger asChild>
<Button variant="secondary">Edit project</Button>
</SheetTrigger>
<SheetContent size="lg">
<SheetHeader>
<SheetTitle>Edit project</SheetTitle>
<SheetDescription>Changes are visible to everyone on your team.</SheetDescription>
</SheetHeader>
<Field>
<FieldLabel>Name</FieldLabel>
<FieldControl>
<Input defaultValue="Werewolf" />
</FieldControl>
</Field>
<SheetFooter>
<SheetClose asChild>
<Button variant="secondary">Cancel</Button>
</SheetClose>
<Button>Save</Button>
</SheetFooter>
</SheetContent>
</Sheet>
<Sheet>
<SheetTrigger asChild>
<Button variant="secondary">Edit project</Button>
</SheetTrigger>
<SheetContent size="full">
<SheetHeader>
<SheetTitle>Edit project</SheetTitle>
<SheetDescription>Changes are visible to everyone on your team.</SheetDescription>
</SheetHeader>
<Field>
<FieldLabel>Name</FieldLabel>
<FieldControl>
<Input defaultValue="Werewolf" />
</FieldControl>
</Field>
<SheetFooter>
<SheetClose asChild>
<Button variant="secondary">Cancel</Button>
</SheetClose>
<Button>Save</Button>
</SheetFooter>
</SheetContent>
</Sheet>Examples
Edit details
import { Button } from "@/components/ui/button"
import { Field, FieldControl, FieldLabel } from "@/components/ui/field"
import { Input } from "@/components/ui/input"
import { Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet"
<Sheet>
<SheetTrigger asChild>
<Button variant="secondary">Edit project</Button>
</SheetTrigger>
<SheetContent>
<SheetHeader>
<SheetTitle>Edit project</SheetTitle>
<SheetDescription>Changes are visible to everyone on your team.</SheetDescription>
</SheetHeader>
<Field>
<FieldLabel>Name</FieldLabel>
<FieldControl>
<Input defaultValue="Werewolf" />
</FieldControl>
</Field>
<SheetFooter>
<SheetClose asChild>
<Button variant="secondary">Cancel</Button>
</SheetClose>
<Button>Save</Button>
</SheetFooter>
</SheetContent>
</Sheet>From the bottom
Short choices on a phone, with the main action one tap away.
import { Button } from "@/components/ui/button"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet"
<Sheet>
<SheetTrigger asChild>
<Button variant="secondary">Sort</Button>
</SheetTrigger>
<SheetContent side="bottom" size="sm">
<SheetHeader>
<SheetTitle>Sort projects</SheetTitle>
</SheetHeader>
<RadioGroup aria-label="Sort projects" defaultValue="recent">
<RadioGroupItem value="recent" label="Recently edited" />
<RadioGroupItem value="name" label="Name" />
</RadioGroup>
</SheetContent>
</Sheet>Do and don't
Do
- Use it for work next to the page: the details of a row, filters, a short form.
- Give it a title, and put the main action in the footer.
- Keep it open while the user works in it; close it when the action is done.
Don't
- Don't use it for a short confirmation; use AlertDialog or Dialog.
- Don't open a sheet from a sheet.
- Don't use it for a menu of actions; use DropdownMenu.
Props
| Name | Type | Values | Default | Description |
|---|---|---|---|---|
| open | boolean | - | - | Set on Sheet. Whether it is open, when you control it. Pair it with onOpenChange. Only on web. |
| defaultOpen | boolean | - | false | Set on Sheet. Opens it on first render, when you don't control it. Only on web. |
| onOpenChange | function | - | - | Set on Sheet. Called with true or false when it opens or closes (trigger, close button, Escape, scrim). Only on web. |
| side | enum | top | right | bottom | left | right | Set on SheetContent. The edge it slides in from. right for details and forms next to a list; left for navigation; bottom for short choices on a phone; top rarely. |
| size | enum | sm | md | lg | full | md | Set on SheetContent. Widest it gets on the left and right (sm 320px, md 448px, lg 672px), tallest on the top and bottom (a third, half, three quarters of the screen). full covers the screen along that side, with no corners. |
| showCloseButton | boolean | - | true | Set on SheetContent. The corner close button. Hide it only when the footer has a Cancel or Close. Only on web. |
Slots
| Name | Required | Description |
|---|---|---|
| trigger | no | SheetTrigger with asChild around a Button. |
| title | yes | SheetTitle, required for a11y. |
| description | no | Optional SheetDescription. |
| content | no | The body: fields, details, a list. It scrolls with the panel. |
| footer | no | SheetFooter with the actions. |
Anatomy
| Part | Description |
|---|---|
| root | Sheet. Holds the open state. |
| overlay | The scrim behind the sheet, the same as Dialog's. |
| content | The panel: raised fill, shadow-high, p-xl, gap-lg. It covers the full length of its side; the border and the page corners are only on the side that faces the content. |
| header | Top block for the title and description, gap-sm. |
| title | The name of the sheet: 16px medium. |
| description | Optional supporting text: 13px secondary. |
| footer | Actions at the end of the panel, however short the content is. |
| close | Icon button in the corner that closes the sheet, the same as Dialog's. |
| icon | Tabler X in the close button, 16px. |
| hitArea | Transparent ::before on the close button, never smaller than size-target-min. |
Accessibility
Role: dialog
| Keys | Action |
|---|---|
| Escape | Closes the sheet and returns focus to the trigger. |
| Tab | Cycles focus inside the sheet (focus trap). |
- A modal dialog: it traps focus, hides the rest of the page from assistive tech and closes on Escape or a scrim press.
- Always provide a SheetTitle (visible or sr-only), so the sheet has a name.
- The corner close is a button with a visually hidden Close label and a Tabler X. With showCloseButton={false}, the footer must have a Cancel or Close.
- It slides in only when the user allows motion; under prefers-reduced-motion it fades in. It closes at once.
- Do not open a sheet from a sheet.
Install
npx shadcn add https://werewolf-ui.vercel.app/r/sheet.jsonThis also adds styles/werewolf.css, the tokens every component uses, and any other component it needs. Import that file once in your root CSS, after @import "tailwindcss", with a path relative to that CSS file (from app/globals.css it is @import "../styles/werewolf.css").