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 sheetComposition
<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 sheetAPI 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
SheetTitleis required (wrap it insr-onlyif you don't want it shown) so the sheet has an accessible name;SheetDescriptionis wired toaria-describedbywhen 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-labelof "Close". When you drop it withshowCloseButton={false}, keep aSheetCloseaction 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.