Skip to content

ComponentsOverlays

Sheet

A modal panel that slides in from any edge of the screen, for navigation, filters, carts and detail views. Built on the same modal as Dialog, so it traps focus, locks scroll and restores focus on close.

React AriaSource

Installation

pnpm dlx shadcn@latest add @desyne/sheet

The CLI installs dependencies and any other components this one uses.

Usage

tsx
import {
  SheetClose,
  SheetContent,
  SheetDescription,
  SheetFooter,
  SheetHeader,
  SheetTitle,
  SheetTrigger,
} from "@/components/ui/sheet";
tsx
<SheetTrigger>
  <Button>Open</Button>
  <SheetContent side="right">
    <SheetHeader>
      <SheetTitle>Title</SheetTitle>
      <SheetDescription>Description</SheetDescription>
    </SheetHeader>
    <div className="p-5">{/* content */}</div>
    <SheetFooter>
      <SheetClose>Cancel</SheetClose>
      <Button>Save</Button>
    </SheetFooter>
  </SheetContent>
</SheetTrigger>

SheetTrigger connects the first child (any pressable, usually a Button) to the SheetContent after it. No state or refs needed.

The body is yours to pad

SheetHeader and SheetFooter bring their own padding and borders, but the panel itself has none, so full-bleed lists and navigation are easy. Wrap the content between them in a div with p-5, and add min-h-0 flex-1 overflow-y-auto when it can grow taller than the screen.

When to use

  • Sheet — secondary or longer content that relates to the current page: filters, a cart, a record's details, mobile navigation.
  • Dialog — a short, focused task or confirmation that belongs in the middle of the screen.
  • Popover — small, non-blocking content anchored to its trigger.
  • Sidebar — persistent navigation that stays on screen.

Anatomy

tsx
<SheetTrigger>
  <Button />                   {/* trigger */}
  <SheetContent>               {/* overlay + sliding panel + dialog */}
    <SheetHeader>
      <SheetTitle />           {/* accessible name */}
      <SheetDescription />
    </SheetHeader>
    <div />                    {/* your content */}
    <SheetFooter>
      <SheetClose />           {/* button that closes the sheet */}
    </SheetFooter>
  </SheetContent>
</SheetTrigger>
PartRendersNotes
SheetTrigger—Manages open state and wires the trigger to the sheet. Same as DialogTrigger.
SheetContentModalOverlay › Modal › DialogBackdrop, sliding panel and the role="dialog" element, a flex column that fills the panel. Adds a close icon by default.
SheetHeader<div>Title and description, with p-5, a bottom border and room for the close icon.
SheetTitleHeading (slot="title")Becomes the sheet's accessible name.
SheetDescription<p>Supporting text.
SheetFooter<div>Actions pinned to the bottom with mt-auto and a top border. Stacked (primary on top) on mobile, right-aligned from sm up.
SheetCloseButton slot="close"Any button with slot="close" closes the sheet. Defaults to outline.
Close icon<button>The top-right ✕. Rendered unless showCloseButton={false}.

Examples

Sides

right (default) and left suit detail panels and navigation; bottom suits mobile action sheets; top suits announcements and search. Each side slides in from its own edge.

Sizes

size sets the maximum width of left and right sheets from the sm breakpoint up: sm (320px), md (384px, default) and lg (576px). On phones they take 75% of the screen. top and bottom sheets are full width and size to their content.

Scrollable content

The panel is always the full height of the screen. Give the content min-h-0 flex-1 overflow-y-auto so it scrolls while the header and footer stay in place.

Forms

Use the render-function child, ({ close }) => …, and wrap everything in a React Aria Form with flex min-h-0 flex-1 flex-col so the footer stays at the bottom. Validation runs before onSubmit; close the sheet once the request succeeds.

Controlled

Render SheetContent without a trigger and control it with isOpen and onOpenChange. Use this to open a detail panel from a list row, a table or a keyboard shortcut. Focus returns to the row that opened it.

Non-dismissable

isDismissable={false} ignores backdrop clicks, isKeyboardDismissDisabled ignores Esc, and showCloseButton={false} hides the ✕. Reserve this for work that must finish, and always leave a visible way out once it has.

Recipes

Filter panel

A left sheet with CheckboxGroup, Slider, RadioGroup and Switch. Edits go into a draft that's only applied on "Show results"; "Reset" restores the defaults, and the trigger shows how many filters are active.

Mobile navigation

A hamburger button that opens a left sheet with navigation links, an account footer and no header. Without a SheetTitle, the sheet is named with aria-label. Links close the sheet through close() from the render function.

Home

Shopping cart

A cart with quantity steppers and a scrollable item list, and a footer that stacks the subtotal above a full-width checkout button.

Accessibility

  • Focus moves into the sheet on open (to the first focusable element, or one with autoFocus), stays trapped inside, and returns to the trigger on close.
  • Page scroll is locked while open, and content outside is hidden from screen readers.
  • SheetTitle provides the accessible name. Without a visible title, pass aria-label to SheetContent.
  • The close icon has aria-label="Close".
  • Nested overlays (selects, menus, popovers) inside a sheet work as expected; Esc closes the innermost one first.

Keyboard

KeyAction
Tab / Shift+TabCycles focus within the sheet
EscCloses the sheet (unless isKeyboardDismissDisabled)

Styling

Data attributes

AttributeOnPresent when
data-enteringoverlay, panelOpening animation is running
data-exitingoverlay, panelClosing animation is running

Enter and exit animations are tw-animate-css slide classes keyed off these attributes (300ms in, 200ms out), so React Aria waits for them before unmounting.

Slots

data-slotElement
sheet-overlayBackdrop
sheet-contentSliding panel
sheet-headerHeader
sheet-footerFooter
dialog-title, dialog-descriptionTitle and description (shared with Dialog)

Customizing

  • className on SheetContent styles the panel: override the width with sm:max-w-md, or cap a bottom sheet with max-h-[80dvh].
  • The panel is fixed flex flex-col bg-popover with a border on the inner edge. The inner dialog is a relative flex h-full flex-col, so children stack from top to bottom and SheetFooter sits at the bottom. It scrolls as a whole as a fallback when you don't give the content its own scroll area.
  • The backdrop reuses overlayStyles from Dialog.

sheetVariants

The tailwind-variants function behind the panel. Use it to build your own edge panels.

tsx
import { sheetVariants } from "@/components/ui/sheet";

sheetVariants({ side: "left", size: "lg" });

API Reference

SheetTrigger

Prop

Type

SheetContent

Prop

Type

Also accepts the other props of React Aria's ModalOverlay.

SheetClose

A Button with slot="close". Accepts every button prop.

Prop

Type

SheetTitle

Accepts every prop of React Aria's Heading, including level to change the heading element.

SheetHeader / SheetFooter / SheetDescription

Accept every prop of <div> / <div> / <p>.

sheetVariants

Prop

Type

  • Dialog — a centered modal for focused tasks.
  • Popover — non-modal content anchored to a trigger.
  • Sidebar — persistent app navigation.
  • Toast — confirm the result after the sheet closes.