Components

Sheet

Side drawer that slides in from any edge (top / bottom / left / right). One of the most-used surfaces in the Yuno Dashboard — long create forms (payment link, rule, blocklist), filter panels, detail editors, quick create/edit forms, properties inspectors. Built on Radix Dialog with Yuno-token chrome (Header + Body + Footer stacked in a flex-col, bg-background, shadow-lg, dimmer bg-foreground/24).

Updated Jul 21, 2026 by Leonardo Posada
Not sure this is the right surface?
Dialog, Sheet, Drawer and full-screen views compete for similar jobs. Read the decision guide to pick the right one for your case. Overlays & full-screen guide.

Anatomy

Five parts. Trigger and Content, plus three composable regions inside the Content: Header (bordered top row with the title + optional back), Body (the scrollable main area), Footer (bordered bottom row for the CTAs). Content stacks all three in a flex-col so the body absorbs the remaining height.

  1. 1
    SheetTrigger

    The button (or any element via asChild) that opens the sheet. Radix wires up the state — no controlled prop needed.

  2. 2
    SheetContent (side)

    The panel. side='right' (default) / 'left' / 'top' / 'bottom'. Right and Left ship with w-full min-w-80 (320) max-w-3xl (768) — defaults to the maximum 768px on desktop and gracefully shrinks (capped by min-w-80) on narrow viewports. Top and Bottom fill the width; bottom has rounded-t-3xl per Yuno mobile convention.

  3. 3
    SheetHeader

    Bordered top row: p-6 border-b flex-col gap-1.5 text-left with pr-14 reserved for the close X. Holds SheetTitle + optional SheetDescription. Matches the Figma spec exactly.

  4. 4
    SheetBody

    The scrollable middle region: flex-1 overflow-auto p-6. Fills the space between Header and Footer. When content overflows, only the body scrolls — Header and Footer stay pinned.

  5. 5
    SheetFooter

    Bordered bottom row: p-6 border-t flex flex-col-reverse gap-2 sm:flex-row sm:justify-end. Anchor the primary CTA on the right (desktop) or full-width on top of the stack (mobile).

  6. 6
    Close X

    Absolutely positioned at top-6 right-6, 16×16 (size-4), rounded-xs, opacity-70 with hover opacity-100 and a focus-visible ring. Rendered automatically by SheetContent — no need to add it manually.

Variants

Four sides. Right is the canonical for detail/edit flows (matches reading direction). Left is common for navigation drawers. Top/Bottom fill the width — bottom sheets get the Yuno rounded-t-3xl mobile treatment.

Right (canonical)
<Sheet>
  <SheetTrigger asChild>
    <Button>Open</Button>
  </SheetTrigger>
  <SheetContent side="right">
    <SheetHeader>
      <SheetTitle>Sheet title</SheetTitle>
    </SheetHeader>
    <SheetBody>{/* content */}</SheetBody>
    <SheetFooter>
      <SheetClose asChild>
        <Button variant="outline">Cancel</Button>
      </SheetClose>
      <Button>Save</Button>
    </SheetFooter>
  </SheetContent>
</Sheet>
Left
<SheetContent side="left">
  <SheetHeader>
    <SheetTitle>Sheet title</SheetTitle>
  </SheetHeader>
  <SheetBody>{/* content */}</SheetBody>
  <SheetFooter>
    <SheetClose asChild>
      <Button variant="outline">Cancel</Button>
    </SheetClose>
    <Button>Save</Button>
  </SheetFooter>
</SheetContent>
Top
<SheetContent side="top">
  <SheetHeader>
    <SheetTitle>Sheet title</SheetTitle>
  </SheetHeader>
  <SheetBody>{/* content */}</SheetBody>
</SheetContent>
Bottom
<SheetContent side="bottom">
  <SheetHeader>
    <SheetTitle>Sheet title</SheetTitle>
  </SheetHeader>
  <SheetBody>{/* content */}</SheetBody>
  <SheetFooter>
    <Button>Confirm</Button>
  </SheetFooter>
</SheetContent>

Width bounds (right and left)

Right and Left sheets ship with a 320px min-width and a 768px max-width. Default is w-full so on desktop the panel expands to the max (768px), giving prototypes the most room without any override needed. Below the max, w-full tracks the viewport down to the 320px floor. Override via className when a screen genuinely needs a slimmer panel (e.g. sm:max-w-md for a compact settings sheet) — but 768 is intentionally the ceiling.

States

Overlay: fade-in 450ms on open, fade-out 200ms on close. Content: slides from the anchored edge — right sheet enters from off-right, left from off-left, top from above, bottom from below. Entry 300ms with ease-out (0.22, 1, 0.36, 1); exit 200ms with ease-in (0.55, 0, 1, 0.45) so the panel accelerates out. SheetBody children cascade in with a 40ms stagger starting at 220ms — form fields, copy blocks and any block-level content land one after another after the sheet finishes sliding. Close X: opacity-70 idle, opacity-100 on hover, focus-visible ring on keyboard focus. Respects prefers-reduced-motion — cascade turns off automatically.

Motion

Sheets use side-specific slide-in/slide-out — never a plain fade — so the direction of entry matches the anchored edge. SheetBody children cascade after the panel lands so the eye follows content in the order it will be edited.

Overlayfade-in 450ms ease-out → fade-out 200ms ease-in (bg-foreground/24, no blur).
Content — right sideslide from translateX(100%) → 0 on open, reverse on close. Entry 300ms ease-out, exit 200ms ease-in.
Content — left sideslide from translateX(-100%) → 0 on open, reverse on close. Same timing as right.
Content — top sideslide from translateY(-100%) → 0 on open, reverse on close. Same timing.
Content — bottom sideslide from translateY(100%) → 0 on open, reverse on close. Same timing.
SheetBody cascadeDirect children fade-up (cascade-in) with 40ms stagger starting at 220ms — after the sheet has finished sliding in. Caps at ~10 children so bulk lists don't animate one-by-one.
Reduced motionprefers-reduced-motion: reduce disables the cascade. The slide still fires (short + purposeful).
Use for creating and editing, not deciding
Sheets are the Yuno pattern for focused creating and editing without leaving the current page — long create forms (payment link, rule, blocklist) and detail edits both live here. If the user has to make a critical yes/no decision, use a Dialog — Sheets are too easy to dismiss for weight-bearing choices.
Sheet vs Drawer (internal terminology)
Internally at Yuno many designers and PMs call this component a "Drawer" — treat the two words as interchangeable when someone is describing a prototype. This is the Sheet you want in 99% of cases. The dedicated Drawer component in the kit (/components/drawer) is a different, mobile-specific primitive (vaul, bottom-anchored with a drag handle) — reach for it only when the prototype needs a native-mobile drag-to-close gesture.

Recipes

Ready-to-copy compositions covering the most common Yuno usages of this atom.

Edit form (canonical)

The most common Sheet pattern in the Yuno Dashboard: side='right' with a form in the body and Cancel + Save CTAs in the footer. Body scrolls independently so long forms don't blow out the layout.

<Sheet>
  <SheetTrigger asChild>
    <Button>Open</Button>
  </SheetTrigger>
  <SheetContent side="right">
    <SheetHeader>
      <SheetTitle>Sheet title</SheetTitle>
    </SheetHeader>
    <SheetBody>
      <div className="grid gap-4">
        <div className="grid gap-1.5">
          <Label htmlFor="field-1">Label</Label>
          <Input id="field-1" placeholder="Value" />
        </div>
        <div className="grid gap-1.5">
          <Label htmlFor="field-2">Notes</Label>
          <Textarea id="field-2" rows={4} />
        </div>
      </div>
    </SheetBody>
    <SheetFooter>
      <SheetClose asChild>
        <Button variant="outline">Cancel</Button>
      </SheetClose>
      <Button>Save</Button>
    </SheetFooter>
  </SheetContent>
</Sheet>
With back button (step-in flow)

A back arrow in the header lets the user step out of a sub-view without closing the whole sheet. Common in multi-step editors. Compose it as an icon Button on the left of SheetHeader.

<SheetContent side="right">
  <SheetHeader className="flex-row items-center gap-3">
    <Button variant="ghost" size="icon" className="-ml-2 size-8">
      <ArrowLeft weight="light" className="size-4" />
    </Button>
    <SheetTitle>Step title</SheetTitle>
  </SheetHeader>
  <SheetBody>{/* step content */}</SheetBody>
</SheetContent>
Filter panel (left)

side='left' for a filter drawer that lives on the same side as the table's row headers. Same Header + Body + Footer shape — only the entry direction changes.

<SheetContent side="left">
  <SheetHeader>
    <SheetTitle>Filters</SheetTitle>
  </SheetHeader>
  <SheetBody>{/* filter controls */}</SheetBody>
  <SheetFooter>
    <SheetClose asChild>
      <Button variant="outline">Clear</Button>
    </SheetClose>
    <Button>Apply</Button>
  </SheetFooter>
</SheetContent>
Bottom mobile sheet

side='bottom' picks up the rounded-t-3xl Yuno mobile treatment automatically. Use for confirmation-style flows on mobile prototypes where a full modal would be too heavy.

<SheetContent side="bottom">
  <SheetHeader>
    <SheetTitle>Confirm?</SheetTitle>
  </SheetHeader>
  <SheetBody>{/* summary */}</SheetBody>
  <SheetFooter>
    <SheetClose asChild>
      <Button variant="outline">Cancel</Button>
    </SheetClose>
    <Button>Confirm</Button>
  </SheetFooter>
</SheetContent>
With icon in Header

For status-carrying Sheets (Info / Warning contexts), place a Phosphor icon (weight='light') to the left of the title inside the SheetHeader row. Use text-primary for informational, text-destructive for warnings. Do NOT tint the whole Sheet — the icon is enough. Same 'flex-row items-center gap-3' pattern as the back-button recipe.

<SheetContent side="right">
  <SheetHeader className="flex-row items-center gap-3">
    <Info weight="light" className="size-4 shrink-0 text-primary" />
    <SheetTitle>Read-only mode</SheetTitle>
  </SheetHeader>
  <SheetBody>
    <p className="text-sm text-foreground/85">
      Your current role can view but not edit this workspace.
    </p>
  </SheetBody>
  <SheetFooter>
    <SheetClose asChild><Button>Got it</Button></SheetClose>
  </SheetFooter>
</SheetContent>
With provider logo in Header

For provider-scoped Sheets (Configure Stripe connection, Edit Adyen route, etc.), load the provider logo from the Yuno CDN and place it inline with the title. Sized at size-6 (24px, bigger than a Phosphor icon because logos are brand marks). CDN pattern: https://icons.prod.y.uno/{slug}_logosimbolo.png. Verified slugs: stripe / adyen / dlocal / payu / mercadopago / 2c2p / visa / mastercard / amex. Never tint the logo.

<SheetContent side="right">
  <SheetHeader className="flex-row items-center gap-3">
    {/* Provider logo from Yuno CDN */}
    <img
      src="https://icons.prod.y.uno/stripe_logosimbolo.png"
      alt="Stripe"
      className="size-6 shrink-0 object-contain"
    />
    <SheetTitle>Configure Stripe connection</SheetTitle>
  </SheetHeader>
  <SheetBody>{/* form fields */}</SheetBody>
  <SheetFooter>
    <SheetClose asChild><Button variant="outline">Cancel</Button></SheetClose>
    <Button>Save changes</Button>
  </SheetFooter>
</SheetContent>
With description (rare)

Adding a SheetDescription is supported but rare — the Dashboard convention is title-only in the header. Reach for a description only when the title alone doesn't carry the context.

<SheetHeader>
  <SheetTitle>Sheet title</SheetTitle>
  <SheetDescription>
    Short helper text that complements the title.
  </SheetDescription>
</SheetHeader>

Import

Full compound import from @/components/ui/sheet — includes the new SheetBody wrapper.

The full compound. SheetDescription is optional — most Dashboard sheets are title-only.
import {
  Sheet,
  SheetBody,
  SheetClose,
  SheetContent,
  SheetDescription,
  SheetFooter,
  SheetHeader,
  SheetTitle,
  SheetTrigger,
} from "@/components/ui/sheet";

Props

SheetContent and the compound parts forward Radix props. The most useful ones for prototypes:

PropTypeDefaultDescription
Sheet.open / onOpenChangeboolean / (open: boolean) => voidControlled open state. Omit both for uncontrolled — Radix handles it. Use for programmatic open (from a menu action, for example).
SheetTrigger.asChildbooleanfalseMerges Radix's trigger props onto the child element instead of rendering a button. Standard shadcn pattern for using your own Button.
SheetContent.side"top" | "right" | "bottom" | "left""right"Anchor edge. Right is canonical for detail editing. Right / Left = w-full min-w-80 max-w-3xl (defaults to 768px on desktop). Top / Bottom = fill the width; bottom picks up rounded-t-3xl automatically.
SheetContent.classNamestringMerged onto the panel. Use for width overrides (e.g. sm:max-w-md for a slimmer sheet). Don't override the padding — SheetHeader / SheetBody / SheetFooter handle it.
SheetHeadercomponentThe bordered top row. Ships p-6 border-b flex-col gap-1.5 text-left with pr-14 reserved for the close X. Contains SheetTitle + optional SheetDescription.
SheetBodycomponentThe scrollable middle region. Ships flex-1 overflow-auto p-6. Fills the space between Header and Footer. Wrap long content here so scroll stays inside the sheet.
SheetFootercomponentThe bordered bottom row. Ships p-6 border-t flex-col-reverse gap-2 sm:flex-row sm:justify-end. Primary CTA goes on the right (desktop); on mobile buttons stack full-width with primary on top. Footer buttons are always default size.
SheetTitlecomponentRequired by Radix for accessibility. text-lg font-semibold. Keep short — the header truncates.
SheetDescriptioncomponentOptional and rare in the Dashboard. text-sm text-muted-foreground. Only add when the title alone doesn't carry the context.
SheetClose.asChildbooleanfalseMerges close behavior onto your own Button. Standard pattern for the Cancel button in the footer.

When to use

  • Focused edits or quick create forms without leaving the current page.
  • Filter or configuration panels that need more room than a Popover.
  • Detail views that would be too heavy as a full page but too much for a Popover.
  • Multi-step step-in flows where a back arrow leads to a nested sub-view inside the sheet.

When not to use

  • Critical yes/no confirmations — use Dialog (or AlertDialog for destructive).
  • Transient feedback — use Toast.
  • Persistent inline state that shouldn't be dismissable by clicking outside — use an inline Card / Panel.
  • Full-page workflows — those belong on a route, not in a sheet.

Usage

Do
  • Anchor a primary CTA in the footer (right-aligned on desktop, full-width stack on mobile).
  • Use side='right' for creating and detail editing — matches the reading direction and the Dashboard convention.
  • Wrap long forms in <SheetBody> so scrolling stays inside the sheet.
  • Keep the header title short (≤ 40 chars) — it truncates otherwise.
  • Override the width via className when a screen genuinely needs more room (e.g. sm:max-w-2xl) — bounded still by max-w-3xl.
Don't
  • Don't stack Sheets — one at a time. Nest a step-in view inside the same sheet using the back button.
  • Don't hide the close X — it's the primary escape hatch.
  • Don't use it for destructive confirmations — use AlertDialog.
  • Don't put critical state that must persist inside a Sheet — it's a transient surface.
  • Don't override the width past max-w-3xl (768px) — beyond that it stops feeling like a sheet.

Related

Cross-links to atoms and patterns you may reach for next.

  • DialogReach for Dialog when the user must decide right now — Sheets are for creating and editing, Dialogs are for deciding.
  • DrawerBottom-anchored variant with a drag handle. Use Drawer for mobile-only bottom sheets; use Sheet for anything that lives on desktop.
  • PopoverReach for Popover when the surface is small and inline. Sheets take over an edge; Popovers hover next to their trigger.
  • Alert dialogFor destructive confirmations — never route those into a Sheet.
  • Scroll areaWrap long body content in ScrollArea if you want the Yuno-token scrollbar instead of the native one.