Light

Sheet

Extends the Dialog component to display content that complements the main content of the screen.

Spec · from metadata

When to use

  • Supplementary content panels (settings, filters, detail views)
  • Side navigation on desktop
  • Form panels that slide in from the right
  • Preview panels for list items
  • Any overlay that should not fully obscure the main content context

When not to use

  • Blocking decisions or destructive confirmations -- use AlertDialog
  • Mobile-first swipeable bottom panels -- use Drawer (vaul-based) instead
  • Quick informational dialogs -- use Dialog
  • Simple action lists -- use DropdownMenu or Popover

Variants

PropValuesDefaultDescription
sidetoprightbottomleftrightWhich edge of the viewport the sheet slides in from.
showCloseButtontruefalsetrueWhether to render the X close button in the top-right corner.

Anti-patterns

Avoid<Sheet>
  <SheetContent>
    {/* No SheetTitle */}
    <p>Some content</p>
  </SheetContent>
</Sheet>
Prefer<Sheet>
  <SheetContent>
    <SheetHeader>
      <SheetTitle>Panel Title</SheetTitle>
    </SheetHeader>
    <p>Some content</p>
  </SheetContent>
</Sheet>

SheetTitle is required for accessibility -- Radix Dialog.Title provides the aria-labelledby association.

Avoid// On mobile, using Sheet for bottom panel
<Sheet>
  <SheetContent side="bottom">...</SheetContent>
</Sheet>
Prefer// On mobile, use Drawer for swipeable bottom panel
<Drawer>
  <DrawerContent>...</DrawerContent>
</Drawer>

Drawer (vaul) provides native swipe-to-dismiss gestures for mobile bottom panels. Sheet side='bottom' lacks touch gesture support.

Accessibility

  • Required ARIAaria-labelledby (via SheetTitle)
  • Screen readerAnnounced as a dialog. Close button includes sr-only 'Close' label. Overlay click dismisses the sheet.
  • ContrastOverlay uses bg-foreground/50 for sufficient contrast against page content.

Token bindings

TokenCategoryUsage
bg-cardcolorSheet content background
bg-foreground/50colorOverlay background
text-foregroundcolorTitle text
text-muted-foregroundcolorDescription text
shadow-lgshadowSheet elevation
duration-500motionOpen animation
duration-300motionClose animation

Import

import {
  Sheet,
  SheetContent,
  SheetDescription,
  SheetHeader,
  SheetTitle,
  SheetTrigger,
  SheetFooter,
  SheetClose,
} from "@timelycare/helix-ui"

Props

interface SheetProps {
  open?: boolean
  defaultOpen?: boolean
  onOpenChange?: (open: boolean) => void
}

interface SheetContentProps {
  side?: "top" | "right" | "bottom" | "left"
  className?: string
  children: React.ReactNode
}

Structure

PartTailwind
Overlaybg-black/80 fixed inset-0
SheetContentbg-background p-6 gap-4 shadow-lg
Side panelsw-96 (384px), full height
Top/bottomFull width, auto height
SheetHeaderflex flex-col gap-2
SheetTitletext-lg font-bold leading-7 text-foreground
SheetDescriptiontext-sm text-muted-foreground
Close buttonabsolute right-4 top-4 size-4 rounded-xs

Positions

SideBorderLayout
rightborder-lFixed right, w-96, full height
leftborder-rFixed left, w-96, full height
topborder-bFixed top, full width
bottomborder-tFixed bottom, full width

Close Icon States

StateImplementation
Defaultopacity-70
Hoveropacity-100
Focusfocus-visible:ring-2 focus-visible:ring-ring

Common Patterns

Basic Sheet

<Sheet>
  <SheetTrigger asChild>
    <Button variant="outline">Open</Button>
  </SheetTrigger>
  <SheetContent>
    <SheetHeader>
      <SheetTitle>Title Text</SheetTitle>
      <SheetDescription>
        This is a sheet description.
      </SheetDescription>
    </SheetHeader>
    <div className="py-4">Content goes here</div>
  </SheetContent>
</Sheet>

With Form

<Sheet>
  <SheetTrigger asChild>
    <Button>Edit Profile</Button>
  </SheetTrigger>
  <SheetContent className="flex flex-col gap-4">
    <SheetHeader>
      <SheetTitle>Edit profile</SheetTitle>
      <SheetDescription>
        Make changes to your profile here.
      </SheetDescription>
    </SheetHeader>
    <div className="flex flex-col gap-4">
      <div className="space-y-2">
        <Label htmlFor="name">Name</Label>
        <Input id="name" placeholder="Placeholder" />
      </div>
      <div className="space-y-2">
        <Label htmlFor="username">Username</Label>
        <Input id="username" placeholder="Placeholder" />
      </div>
    </div>
    <SheetFooter>
      <Button type="submit">Button</Button>
    </SheetFooter>
  </SheetContent>
</Sheet>

Top/Bottom Position

<Sheet>
  <SheetTrigger asChild>
    <Button variant="outline">From Top</Button>
  </SheetTrigger>
  <SheetContent side="top">
    <SheetHeader>
      <SheetTitle>Title Text</SheetTitle>
      <SheetDescription>This is a sheet description.</SheetDescription>
    </SheetHeader>
    <div className="py-4">Content</div>
    <Button>Button</Button>
  </SheetContent>
</Sheet>

Mobile Navigation

<Sheet>
  <SheetTrigger asChild>
    <Button variant="outline" size="icon">
      <Menu className="h-4 w-4" />
    </Button>
  </SheetTrigger>
  <SheetContent side="left">
    <SheetHeader>
      <SheetTitle>Navigation</SheetTitle>
    </SheetHeader>
    <nav className="flex flex-col gap-2 py-4">
      <a href="#" className="text-sm">Home</a>
      <a href="#" className="text-sm">About</a>
      <a href="#" className="text-sm">Contact</a>
    </nav>
  </SheetContent>
</Sheet>

Accessibility

  • Built on Radix Dialog — same a11y model as Dialog
  • Focus trapped within sheet when open; returns to trigger on close
  • Keyboard: Escape to close, Tab/Shift+Tab to cycle focus
  • Screen reader: announces as dialog role with aria-modal="true"
  • Title linked via aria-labelledby

Gotchas

ProblemSolution
Trigger not clickableAdd asChild prop to SheetTrigger
Content overflowsAdd overflow-y-auto to content
Width too wide/narrowOverride with className="w-[500px]"
Footer at bottomUse flex flex-col and mt-auto on footer

See Also


Last updated: February 9, 2026