Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Sheet

An edge-anchored panel backed by Radix Dialog — slides in from any side over a scrim, traps focus, and closes on Esc or overlay click. Composed via statics.

Import

import { Sheet, Button } from "@roger-emerson/ploxum-primitives";

Usage

Trigger-based (uncontrolled) — wrap your own Button with Sheet.Trigger asChild:

<Sheet>
  <Sheet.Trigger asChild>
    <Button variant="secondary">Edit profile</Button>
  </Sheet.Trigger>
  <Sheet.Content side="right" size={480}>
    <Sheet.Header>
      <Sheet.Title>Edit profile</Sheet.Title>
      <Sheet.Description>Make changes and save when you are done.</Sheet.Description>
    </Sheet.Header>
    <div className="flex flex-1 flex-col gap-4 px-4">
      {/* form fields */}
    </div>
    <Sheet.Footer>
      <Sheet.Close asChild>
        <Button variant="ghost">Cancel</Button>
      </Sheet.Close>
      <Sheet.Close asChild>
        <Button variant="primary">Save changes</Button>
      </Sheet.Close>
    </Sheet.Footer>
  </Sheet.Content>
</Sheet>

Controlled — drive open / onOpenChange yourself (e.g. opening a detail panel from a row click):

const [open, setOpen] = useState(false);
 
<Sheet onOpenChange={setOpen} open={open}>
  <Sheet.Content side="right" size={480}>
    <Sheet.Header>
      <Sheet.Title>Incident INC-2041</Sheet.Title>
      <Sheet.Description>Gate 4 — acknowledged 12:04:21</Sheet.Description>
    </Sheet.Header>
    {/* detail body */}
  </Sheet.Content>
</Sheet>;

Props

Sheet (root)

PropTypeDefaultDescription
openbooleanControlled open state.
defaultOpenbooleanUncontrolled initial state.
onOpenChange(open: boolean) => voidOpen-state callback (fires on Esc, overlay click, and Sheet.Close).
childrenReactNodeTrigger (optional) + Content.

Sheet.Content

PropTypeDefaultDescription
side"right" | "left" | "top" | "bottom""right"Edge the panel docks to.
sizenumber | string420 (left/right) · "50vh" (top/bottom)Width for left/right, height for top/bottom. Numbers are px. Panel is capped at max-w-full.
hideClosebooleanfalseHides the built-in top-right close button.
closeLabelstring"Close"aria-label for the close button.
classNamestringMerged onto the panel surface.

Sheet.Trigger / Sheet.Close

Radix Dialog.Trigger / Dialog.Close passthroughs. Use asChild to wrap your own Button. Any number of Sheet.Close elements may sit inside the content (e.g. both Cancel and Save in the footer).

Sheet.Header / Sheet.Footer

Thin <div> wrappers (forward all native div props). Header renders a bottom-bordered title block at the top; Footer renders a top-bordered, right-aligned action bar pinned to the bottom (mt-auto).

Sheet.Title / Sheet.Description

Wrap Radix Dialog.Title / Dialog.Description — they register the accessible name/description for the dialog, so include them (or an equivalent aria-label). Forward all underlying Radix props plus className.

Design rules

  • The slide is the one sanctioned motion. Content springs in from the docked edge (spring, stiffness 300 / damping 32). Under prefers-reduced-motion it degrades to an opacity-only fade — no translation. Don't add secondary entrance flourishes.
  • The scrim is the one sanctioned backdrop surface. A flat --ploxum-color-background-scrim overlay sits behind the panel. Never blur the content behind it — glassmorphism on content surfaces is banned.
  • Focus is trapped and returns on close. Radix moves focus into the panel on open and restores it to the trigger on close. Esc and overlay click both dismiss.
  • Default width is 420px. Override via size for denser detail panels (e.g. 480); keep it within one canvas column so the focal map stays legible behind the scrim.
  • Sheet is for edge-anchored panels; Dialog is for centered modals. Reach for Sheet when the content is a side rail, drawer, or console (detail drawers, navigation, log consoles). Reach for Dialog when the interaction is a centered, focused decision.

Storybook

Open Sheet in Storybook ↗