Skip to content

Sheet

Slides a panel in from the edge of the screen for a task beside the page.

Library
What’s this?

Installation

pnpm dlx shadcn@latest add @wisty/sheet

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
        └── SheetClose
import { 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

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.
  • SheetHeader is slds-panel__header, SheetTitle is slds-panel__header-title and the close button is slds-panel__close.
  • The body is slds-panel__body, and px-3 matches its padding.
  • SheetFooter is slds-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.
  • SheetDescription has no SLDS counterpart.
  • Icons are lucide's, not SLDS's utility icons.
PropValueSLDSNotes
SheetContent siderightslds-panel slds-panel_docked slds-panel_docked-rightFull height, 3/4 of the screen up to 20rem (slds-size_medium).
SheetContent sideleftslds-panel slds-panel_docked slds-panel_docked-leftFull height, 3/4 of the screen up to 20rem (slds-size_medium).
SheetContent sidetopslds-panelSLDS has no top panel. The same panel, full width, as tall as its content, with the shadow below it.
SheetContent sidebottomslds-panelSLDS has no bottom panel. The same panel, full width, as tall as its content, with the shadow above it.
SheetContent showCloseButtontrueslds-panel__closeA bare icon button at the header's end.

Accessibility

  • Always include a SheetTitle; it's the sheet's name. Without a SheetDescription, pass aria-describedby={undefined} to SheetContent, 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 a SheetClose.
  • 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, like className="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, and side="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.