RadCN
Overlaysreadycomponentready

Sheet

A modal side panel primitive with trigger, portal, overlay, side placement, header, footer, close controls, and focus management.

Importimport { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from 'radcn/sheet'
Preview

Live package example

Render the upstream profile Sheet and four-side Sheet examples with RadCN modal primitives and app-owned form controls.

Demo and Side

Render the upstream profile Sheet and four-side Sheet examples with RadCN modal primitives and app-owned form controls.

Preview
import { Button } from 'radcn/button'
import { Input } from 'radcn/input'
import { Label } from 'radcn/label'
import {
  Sheet,
  SheetClose,
  SheetContent,
  SheetDescription,
  SheetFooter,
  SheetHeader,
  SheetOverlay,
  SheetPortal,
  SheetTitle,
  SheetTrigger,
} from 'radcn/sheet'

const sheetSides = ['top', 'right', 'bottom', 'left'] as const

export function SheetPreview() {
  return (
    <>
      <Sheet id="profile-sheet">
        <SheetTrigger class="radcn-button radcn-button--outline">Open</SheetTrigger>
        <SheetPortal>
          <SheetOverlay />
          <SheetContent>
            <SheetHeader>
              <SheetTitle>Edit profile</SheetTitle>
              <SheetDescription>Make changes to your profile here. Click save when you're done.</SheetDescription>
            </SheetHeader>
            <Label for="sheet-demo-name">Name</Label>
            <Input id="sheet-demo-name" value="Pedro Duarte" />
            <Label for="sheet-demo-username">Username</Label>
            <Input id="sheet-demo-username" value="@peduarte" />
            <SheetFooter>
              <Button type="submit">Save changes</Button>
              <SheetClose class="radcn-button radcn-button--outline">Close</SheetClose>
            </SheetFooter>
          </SheetContent>
        </SheetPortal>
      </Sheet>

      <div>
        {sheetSides.map((side) => (
          <Sheet id={`profile-sheet-${side}`}>
            <SheetTrigger class="radcn-button radcn-button--outline">{side}</SheetTrigger>
            <SheetPortal>
              <SheetOverlay />
              <SheetContent side={side}>
                <SheetHeader>
                  <SheetTitle>Edit profile</SheetTitle>
                  <SheetDescription>Make changes to your profile here. Click save when you're done.</SheetDescription>
                </SheetHeader>
                <Label for={`sheet-side-${side}-name`}>Name</Label>
                <Input id={`sheet-side-${side}-name`} value="Pedro Duarte" />
                <Label for={`sheet-side-${side}-username`}>Username</Label>
                <Input id={`sheet-side-${side}-username`} value="@peduarte" />
                <SheetFooter><SheetClose>Save changes</SheetClose></SheetFooter>
              </SheetContent>
            </SheetPortal>
          </Sheet>
        ))}
      </div>
    </>
  )
}

Installation

Intended future install command. RadCN is private and not published to npm yet, so this snippet documents the target user-facing API rather than something external consumers can run today.

pnpm add radcn # intended future package
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from 'radcn/sheet'

Theming

RadCN tokens read the resolved theme from the document. Store the user's preference separately, then resolve system preferences to a concrete light or dark theme before setting package tokens.

<html data-radcn-theme-mode="system" data-radcn-theme="dark">
  ...
</html>

Accessibility

  • SheetContent is enhanced with role="dialog", aria-modal, aria-labelledby, and aria-describedby from visible title and description parts.
  • Sheet focus is trapped while open, returns to the trigger when closed, and locks body scrolling during the modal session.
  • Escape, overlay pointer dismissal, default close button, and explicit SheetClose controls close dismissible sheets.
  • Profile inputs and labels remain native Input and Label composition inside the sheet surface.

Customization

  • Sheet exposes root, trigger, portal, overlay, content, header, title, description, footer, and close hooks through data-radcn-sheet* attributes.
  • Side placement maps to side="top", side="right", side="bottom", and side="left" on SheetContent.
  • Button, Input, and Label composition stays app-owned while Sheet owns the modal side panel behavior.
  • Grid layout, padding, text alignment, widths, and repeated side examples map to class, style, CSS variables, or app CSS rather than Tailwind utilities.

Remix 3 Notes

  • React props, Radix Dialog primitives, className, data-slot, Tailwind utilities, cn, and vendor source map to explicit RadCN props, class, public data hooks, package CSS, inline style, and CSS variables.
  • asChild maps to explicit SheetTrigger and SheetClose composition with RadCN Button styling instead of React Slot behavior.
  • SHEET_SIDES, React keys, and repeated rendering map to deterministic server-rendered markup with valid unique ids.
  • Form submit actions, profile persistence, and input state remain app-owned; Sheet owns the modal surface and close behavior.
  • vendor source remains read-only evidence and is not imported by RadCN.