Skip to content

Sheet

Stable

A panel that slides in from the edge of the screen for longer forms, details and filters, while keeping the page underneath in context.

Installation

$ pnpm dlx shadcn@latest add https://www.prfct.dev/r/sheet.json

Usage

import {
  Sheet,
  SheetBody,
  SheetClose,
  SheetContent,
  SheetDescription,
  SheetFooter,
  SheetHeader,
  SheetTitle,
  SheetTrigger,
} from "@/components/ui/sheet"
<Sheet>
  <SheetTrigger render={<Button variant="outline" />}>Edit issue</SheetTrigger>
  <SheetContent>
    <SheetHeader>
      <SheetTitle>Edit issue</SheetTitle>
      <SheetDescription>DAT-248 · Opened by Ada Lovelace</SheetDescription>
    </SheetHeader>
    <SheetBody>{/* scrollable content */}</SheetBody>
    <SheetFooter>
      <SheetClose render={<Button variant="outline" />}>Cancel</SheetClose>
      <Button>Save issue</Button>
    </SheetFooter>
  </SheetContent>
</Sheet>

A sheet is a Dialog that lives on an edge. It shares the same behavior — focus trap, scroll lock, Esc to close — and adds a three-part layout: a fixed SheetHeader, a scrolling SheetBody and a SheetFooter pinned to the bottom.

Examples

Sides

side chooses the edge: right (the default) for details and editing, left for navigation, top and bottom for short, full-width content. Sheets on the left or right are 26rem wide, never closer than 3rem to the opposite edge; sheets on the top or bottom take at most 85% of the viewport height.

Floating

variant="floating" insets the sheet from the viewport by 8px and rounds all its corners, so it reads as a surface above the page rather than part of the window frame. It suits lightweight panels such as notifications.

Filters

Filters are the canonical sheet: many controls, applied to a list that stays visible behind the scrim. Put the result count in the primary action so people know what they'll get before they commit.

On small screens, the app navigation moves into a left sheet opened from a menu button. Mark the current page with aria-current="page".

Width

The width is a regular utility, so override it with className — for example className="w-[min(40rem,calc(100%-3rem))]" for a wide detail view.

Guidelines

When to use

  • To edit or inspect an item from a list without losing your place in it.
  • For forms that are too long for a dialog but don't deserve their own page.
  • For secondary navigation and filters on small screens.

When not to use

  • For a quick decision or a short form — use a Dialog.
  • For mobile bottom panels with swipe gestures and snap points — use a Drawer.
  • For permanent side navigation on large screens — use a Sidebar.

Structure

Keep the header and footer short so the body gets the space. The title names the object, the description adds one line of context (an ID, an owner, a count), and the footer holds at most two actions plus an optional Reset on the far side.

Do.Actions live in the pinned footer, where they're always reachable.
Don’t.Actions at the end of a long body scroll out of view.

Don’t stack

Opening a dialog on top of a sheet is acceptable for confirmations. Opening a sheet on top of a sheet is not — replace the content, or move to a page.

Accessibility

A sheet is a modal dialog with role="dialog", labeled by SheetTitle and described by SheetDescription.

KeyBehavior
EnterSpace
On the trigger: opens the sheet and moves focus into it.
Tab
Moves through the sheet's controls. Focus is trapped until it closes.
ShiftTab
Moves focus backwards, wrapping at the start.
Esc
Closes the sheet and returns focus to the trigger.
  • Always include a title. Use className="sr-only" on SheetTitle when the design shows something else, such as a logo.
  • The close button (showCloseButton, on by default) is how touch screen reader users leave. Keep it, or put a SheetClose in the footer.
  • Reading order. The header, body and footer follow DOM order, so the footer's actions come after the content for screen readers and keyboard users — the same order people see.
  • Motion. Sheets slide in over 320ms with the enter easing and leave in 160ms. With reduced motion, they appear in place.

API reference

Sheet

The root. Doesn't render an element. Accepts every prop of Base UI Dialog.Root: open, defaultOpen, onOpenChange, modal, disablePointerDismissal, actionsRef.

SheetContent

Renders the portal, scrim and panel.

PropTypeDefault
side

The edge the sheet slides in from.

"top" | "right" | "bottom" | "left""right"
variant

floating insets the panel 8px from the viewport and rounds every corner.

"default" | "floating""default"
showCloseButton

Renders an icon button in the top-right corner that closes the sheet.

booleantrue
initialFocus

Element to focus when it opens.

boolean | RefObject | (interaction) => HTMLElement | booleanNo default
finalFocus

Element to focus when it closes.

boolean | RefObject | (interaction) => HTMLElement | booleanNo default
className

Merged with the panel classes. Override the width of left and right sheets with w-*.

stringNo default

SheetHeader, SheetBody, SheetFooter

SheetHeader holds the title and description and leaves room for the close button. SheetBody grows to fill the panel and scrolls on its own. SheetFooter is pinned to the bottom with a hairline border; it stacks on small screens and right-aligns from sm up.

SheetTitle, SheetDescription

SheetTitle renders an <h2> and names the sheet. SheetDescription renders a <p> and describes it.

SheetTrigger, SheetClose

Open and close the sheet. Both render a <button>; use render to render a prfct Button.