Morpha UI

Sheet

A panel docked to an edge of the screen, for content complementary to the page.

Overview

A sheet is the Material-3 side sheet: a surface anchored to one edge of the viewport that holds content complementary to the page — filters, details, a navigation panel. It is built on the Base UI Dialog primitive — focus trap, scroll-lock and Esc dismissal come from the primitive — and styled to the kit's sheet surface: surface-container-low, a hairline outline on the inner edge, the elevation-3 shadow and the same scrim/32 backdrop as the dialog. The headline is type-h5 and the supporting text type-body-sm, so a sheet and a dialog reading the same content look the same.

It is flush with its edge: the outer corners stay square and only the inner ones are rounded (28px). That is what tells it apart from the Drawer, which floats 12px in from the edges and is swipe-dismissed. Reach for the drawer when the surface should be draggable (touch, bottom sheets, snap points); reach for the sheet for the docked panel that opens, stays put and closes.

$ npx morpha-ui add sheet

Composition

<Sheet>
  <SheetTrigger render={<Button variant="tonal">Open</Button>} />
  <SheetContent side="right">
    <SheetHeader>
      <SheetTitle />
      <SheetDescription />
    </SheetHeader>
    <SheetBody /> {/* optional — the scrolling region */}
    <SheetFooter>
      <SheetClose render={<Button variant="text">Close</Button>} />
    </SheetFooter>
  </SheetContent>
</Sheet>

SheetContent renders the portal, the backdrop, the popup and the close button for you. SheetOverlay and SheetPortal are exported too, for the rare case you need to assemble the surface yourself.

Examples

Side

side takes top, right (the default), bottom or left. The rounded corners, the border and the enter transition all follow the edge, and the horizontal sides are logical — under dir="rtl" left and right mirror with the document.

Custom size

A side sheet is 75% wide on mobile and 24rem from sm up; a top/bottom sheet is as tall as its content. Override the size on the axis the sheet is docked to: w-* for a left/right sheet, h-* for a top/bottom one.

No close button

showCloseButton={false} drops the top-right "X" — the footer actions carry the dismissal instead (the scrim and Esc still work). The header reclaims the space the button reserved.

Scrollable content

Put long content in SheetBody; it scrolls while the header and the footer stay fixed. Add divided to draw the dividers that separate the scroll region.

Controlled

Drive the sheet yourself with open / onOpenChange — useful when an action must run before it closes, or when there is no trigger to click at all.

No filters applied.

RTL

Wrap the app in DirectionProvider and set dir="rtl". The sheet docks to the mirrored edge and the header, close button and footer actions all follow — the paddings are logical, not physical. Because the content is portalled, set dir on SheetContent too (or on <html>).

Styling

State lives on data attributes: data-side, data-close-button, plus the usual data-starting-style / data-ending-style for the enter and exit transitions. The enter slides the surface in from its edge with the kit's strong ease-out; under prefers-reduced-motion only the fade remains.

Coming from shadcn? Same parts and the same side / showCloseButton props, plus a SheetBody for the scrolling region. The close "X" is an IconButton, not a ghost Button, and the surface is the MD3 sheet (surface-container-low, 28px inner corners) rather than the popover surface.

Installation

Morpha UI components are distributed via the Morpha CLI: npx morpha-ui add sheet.

npx morpha-ui add sheet

API Reference

SheetContent adds the props below; all Base UI Dialog.Popup props are forwarded.

Prop

Type

SheetBody — the scrolling region — takes one extra prop:

Prop

Type

The Sheet root forwards every Base UI Dialog.Root prop (open / onOpenChange, defaultOpen, modal, …):

Prop

Type

SheetTrigger, SheetClose, SheetTitle and SheetDescription forward their Base UI props; SheetHeader and SheetFooter forward div props.

Accessibility

  • A SheetTitle is required (wrap it in sr-only if you don't want it shown) so the sheet has an accessible name; SheetDescription is wired to aria-describedby when present.
  • Focus is trapped while the sheet is open and restored to the trigger on close; Esc dismisses it.
  • The close button carries an aria-label of "Close". When you drop it with showCloseButton={false}, keep a SheetClose action in the footer so the sheet is dismissible without the keyboard shortcut.

The sheet is rendered in a portal on document.body, so inside the preview it follows the site theme rather than the frame's light/dark toggle.

On this page