Sheet
Slides a panel in from the edge of the screen for a task beside the page.
Installation
The CLI installs the Radix or Base UI file to match your project, with what it builds on: button.
If your project doesn't have Wisty UI's theme yet, add it once with npx shadcn@latest add @wisty/cosmos. Until then, components keep shadcn's default look. Installation has the details.
When to use
Use it
- To look at or edit a record's details without leaving the page, like a contact from a list.
- For filters or settings that act on the page underneath, as the List view filters example shows.
- For a short form that's easier to fill in beside the content than in a centered dialog.
Don't use it
- For a question people must answer before going on. Use Alert dialog.
- For a short task that needs no context from the page. Use Dialog.
- For a few details next to one element. Use Popover or Hover card.
- For navigation that stays on screen. Use Sidebar.
- For a panel people work alongside. The sheet is modal: the page behind it can't take focus while it's open.
Usage
Sheet
├── SheetTrigger
└── SheetContent
├── SheetHeader
│ ├── SheetTitle
│ └── SheetDescription
└── SheetFooter
└── SheetCloseimport { buttonVariants } from "@/components/ui/button"
import {
Sheet,
SheetContent,
SheetHeader,
SheetTitle,
SheetTrigger,
} from "@/components/ui/sheet"<Sheet>
<SheetTrigger className={buttonVariants({ variant: "outline" })}>
Details
</SheetTrigger>
<SheetContent aria-describedby={undefined}>
<SheetHeader>
<SheetTitle>Details</SheetTitle>
</SheetHeader>
<div className="px-3">The details go here.</div>
</SheetContent>
</Sheet>Put the body between the header and the footer, padded with px-3 so it lines up with the header. SheetContent draws a close button in its top corner, level with the title; showCloseButton={false} leaves it out.
A SheetClose closes the sheet on its own, so use it for Cancel. To close after a button has done its work, like Apply, pass open and onOpenChange from state to Sheet and set it closed in the handler.
Examples
Sides
A panel from each side of the screen, set with `side` on `SheetContent`.
API reference
| Part | Built on |
|---|---|
Sheet | Radix Dialog.Root (opens in a new tab) |
SheetTrigger | Radix Dialog.Trigger (opens in a new tab) |
SheetClose | Radix Dialog.Close (opens in a new tab) |
SheetContent | Radix Dialog.Content (opens in a new tab) |
SheetHeader | <div> (opens in a new tab) |
SheetFooter | <div> (opens in a new tab) |
SheetTitle | Radix Dialog.Title (opens in a new tab) |
SheetDescription | Radix Dialog.Description (opens in a new tab) |
SheetContent
| Prop | Type | Default |
|---|---|---|
side | "top" | "right" | "bottom" | "left" | "right" |
showCloseButton | boolean | true |
SLDS mapping
SLDS has no sheet. The sheet is its side panel, slds-panel slds-panel_docked slds-size_medium, floated over the page.
- The backdrop is
slds-backdrop, as in Dialog. SheetHeaderisslds-panel__header,SheetTitleisslds-panel__header-titleand the close button isslds-panel__close.- The body is
slds-panel__body, andpx-3matches its padding. SheetFooterisslds-panel__actions.
Known gaps:
- SLDS docks its panels inside the page layout, beside the content, with no backdrop. The sheet floats over the page and dims it. A docked panel that reflows the page is your layout.
SheetDescriptionhas no SLDS counterpart.- Icons are lucide's, not SLDS's utility icons.
| Prop | Value | SLDS | Notes |
|---|---|---|---|
SheetContent side | right | slds-panel slds-panel_docked slds-panel_docked-right | Full height, 3/4 of the screen up to 20rem (slds-size_medium). |
SheetContent side | left | slds-panel slds-panel_docked slds-panel_docked-left | Full height, 3/4 of the screen up to 20rem (slds-size_medium). |
SheetContent side | top | slds-panel | SLDS has no top panel. The same panel, full width, as tall as its content, with the shadow below it. |
SheetContent side | bottom | slds-panel | SLDS has no bottom panel. The same panel, full width, as tall as its content, with the shadow above it. |
SheetContent showCloseButton | true | slds-panel__close | A bare icon button at the header's end. |
Accessibility
- Always include a
SheetTitle; it's the sheet's name. Without aSheetDescription, passaria-describedby={undefined}toSheetContent, or the console may warn. - An icon-only trigger needs an
aria-label, as the List view filters example shows. - With
showCloseButton={false}, give the sheet another way out, like a Cancel that's aSheetClose. - With a combobox open inside, Escape closes only its list, and the next Escape closes the sheet. A command's search field isn't an open list, so Escape there closes the sheet at once.
Motion
- The sheet slides in from its side and the backdrop fades in, in every theme.
- With reduced motion the sheet appears in place. The backdrop still fades.
Responsive behavior
- Side sheets are 3/4 of the screen wide, up to 20rem on screens 640px and wider. For a wider one, pass a width through
className, likeclassName="sm:max-w-120". - Top and bottom sheets are as wide as the screen and as tall as their content.
- The sheet scrolls when its content is taller than the screen, and footer buttons wrap when they don't fit.
Performance
- The content mounts only while the sheet is open, in a portal on
document.body. Styles scoped to an ancestor don't reach it, and its state resets on close.
Rules
- Prefer
side="right"for details and editing, andside="left"for navigation or filters. Top and bottom have no Salesforce counterpart, so use them sparingly. - Put Cancel before the main action in
SheetFooter. - Don't restyle the panel's background, shadow or corners. They come from the theme tokens.
Related
- DialogOpens a window over the page for a short task or a message that needs an answer.
- Alert dialogAsks a question people must answer before going on, like confirming a delete.
- PopoverShows details or a short form in a panel anchored to a trigger.
- SidebarLays out an app's side navigation, with groups, a header and a footer.