Skip to content
HyperDesign System

Sheet

A dialog pinned to an edge of the viewport, for secondary work that keeps the page in view: settings, details, mobile navigation. Same focus trap and Esc behaviour as Dialog. Source: src/components/ui/sheet.tsx.

Sides

Four edges. Right is the default for detail and settings panels; left is navigation (the docs' own mobile menu); bottom is a phone action sheet; top is rare.

Form panel

Header and footer stay pinned while the body scrolls. Hairlines mark the two edges. Save submits the form and closes the sheet.

Without the corner close

showCloseButton={false} drops the X when the footer already has a clear way out, and overlayClassName adjusts the scrim for one sheet without touching the rest.

Motion and layering

The slide and fade are CSS keyframes (tw-animate), so a sheet closed mid-slide does not reverse smoothly, and there is no drag-to-dismiss. Under reduced motion the global rule cuts both to near-zero.

Sheet motion and layering tokens
ValueTokenUsage
50Scrim and panel. The brand ladder (--z-modal 200) is defined but not wired in yet.
220msEnter: the panel slides 40px (slide-in-from-*-10) and fades in.
90msExit: faster than the entrance.
quint-outThe panel's curve, for a larger move than a popover's.
200msThe scrim's fade, shared with Dialog.
3/4 · 24remLeft and right sheets: 75% of a phone, capped at 384px from sm.

Props

Sheet parts and props
Part / propTypeDefaultDescription
SheetRadix Dialog.Root—open, defaultOpen, onOpenChange, modal.
SheetTriggerRadix Dialog.Trigger—Use asChild around a Button. Focus returns here on close.
SheetContentRadix Dialog.Content—Portals itself with its own scrim. A flex column with a 16px gap.
side"top" | "right" | "bottom" | "left""right"Which edge it is pinned to. Mirrored as data-side.
showCloseButtonbooleantrueA ghost icon-sm X at top-right with a sr-only “Close”.
overlayClassNamestring—Merged onto the scrim, e.g. backdrop-blur-none for one sheet.
SheetHeaderComponentProps<"div">—16px padded stack for title and description.
SheetTitleRadix Dialog.Title—Required for the accessible name. 18px heading face.
SheetDescriptionRadix Dialog.Description—Wired to aria-describedby.
SheetFooterComponentProps<"div">—Pushed to the bottom (mt-auto), stacked. Add sm:flex-row for a button row.
SheetCloseRadix Dialog.Close—Any element that closes the sheet. Use asChild around a Button.

Best practices

When to use

  • A sheet keeps context: the user can see what they are editing behind it. Use a Dialog when the page should step back.
  • Navigation on a phone is a left sheet. Long detail views and filters are a right sheet.
  • Don't stack a sheet on a sheet. Navigate inside it, or close it and open a dialog.

Behavior

  • Esc, a scrim click, the X and any SheetClose all close it; focus returns to the trigger.
  • Pin the header and footer and scroll the body, so the primary action is always reachable.
  • Bottom sheets keep the safe-area in mind: put the primary action last and full width.

Accessibility

  • Every sheet needs a SheetTitle; sr-only it if the design hides it.
  • The X is labelled “Close” for screen readers. A custom close button needs the same.
  • Focus is trapped inside while open; tab order follows the DOM, so keep the footer last.

Content

  • Title the sheet after the object or task (“Edit schedule”, “Run 4182”).
  • Keep sheets to one task. If it needs tabs and a wizard, it wants its own page.