Light

Drawer

A drawer component for React.

Spec · from metadata

When to use

  • Mobile-first bottom panels with swipe-to-dismiss
  • Mobile action sheets and option pickers
  • Responsive overlays that should feel native on touch devices
  • Any edge panel where swipe gestures improve UX

When not to use

  • Desktop-only side panels -- use Sheet instead (more conventional desktop pattern)
  • Destructive confirmations -- use AlertDialog
  • Simple dropdown actions -- use DropdownMenu
  • Non-overlay content -- use collapsible or accordion

Anti-patterns

Avoid// Desktop settings panel
<Drawer>
  <DrawerContent>
    <DrawerTitle>Settings</DrawerTitle>
    {/* complex settings form */}
  </DrawerContent>
</Drawer>
Prefer// Desktop settings panel
<Sheet>
  <SheetContent>
    <SheetTitle>Settings</SheetTitle>
    {/* complex settings form */}
  </SheetContent>
</Sheet>

Sheet is the standard pattern for desktop side panels. Drawer is optimized for mobile touch interactions. Use responsive composition if you need both.

Avoid<Drawer direction="right">
  <DrawerContent>
    {/* No DrawerTitle */}
    <p>Content</p>
  </DrawerContent>
</Drawer>
Prefer<Drawer direction="right">
  <DrawerContent>
    <DrawerHeader>
      <DrawerTitle>Panel Title</DrawerTitle>
    </DrawerHeader>
    <p>Content</p>
  </DrawerContent>
</Drawer>

DrawerTitle is required for accessibility. vaul's Drawer.Title provides the aria-label association.

Accessibility

  • Required ARIAaria-labelledby (via DrawerTitle)
  • Screen readerAnnounced as a dialog. Drag handle is decorative. Overlay click dismisses the drawer.
  • ContrastOverlay uses bg-foreground/50 for sufficient contrast against page content.

Token bindings

TokenCategoryUsage
bg-cardcolorDrawer content background
bg-foreground/50colorOverlay background
bg-mutedcolorDrag handle bar
text-foregroundcolorTitle text
text-muted-foregroundcolorDescription text

Import

import {
  Drawer,
  DrawerTrigger,
  DrawerContent,
  DrawerHeader,
  DrawerTitle,
  DrawerDescription,
  DrawerFooter,
  DrawerClose,
} from "@timelycare/helix-ui"

Props

interface DrawerProps {
  open?: boolean
  onOpenChange?: (open: boolean) => void
  shouldScaleBackground?: boolean
  children: React.ReactNode
}

interface DrawerContentProps {
  className?: string
  children: React.ReactNode
}

Design Tokens

Drawer Content

PropertyTokenValue
Backgroundbg-background#fafafa
Borderborder1px solid border color
Border radius (top)rounded-t-[10px]10px
Content max-widthmax-w-sm384px

Handle/Indicator

PropertyTokenValue
Backgroundbg-muted#f4f4f5
Heighth-28px
Widthw-[120px]120px
Border radiusrounded-full9999px
Top paddingpt-416px

Drawer Header

PropertyTokenValue
Paddingp-416px
Max widthmax-w-sm384px
Gapgap-1.56px between title and description
Alignment (md)text-leftLeft-aligned
Alignment (sm)text-centerCenter-aligned

Typography

ElementSizeColorWeight
Titletext-lg (18px)text-foregroundsemibold
Descriptiontext-sm (14px)text-muted-foregroundnormal

Drawer Footer

PropertyTokenValue
Paddingp-416px
Max widthmax-w-sm384px
Gapgap-28px
Button widthw-fullFull width
Button orderPrimary first, Cancel second

Buttons

PropertyTokenValue
Heighth-936px
Paddingpx-4 py-216px horizontal, 8px vertical
Border radiusrounded-md8px
Shadowshadow-xsSubtle drop shadow

Increment Buttons (Statistic)

PropertyTokenValue
Sizesize-832px × 32px
Border radiusrounded-fullCircle
Borderborder border-input1px solid input color
Backgroundbg-input#fafafa
Shadowshadow-xsSubtle drop shadow
Icon sizesize-416px × 16px
Icon colortext-primary#19518b

Statistic Value

PropertyTokenValue
Font sizetext-7xl72px
Font weightfont-boldbold
Letter spacingtracking-tighter-0.8px
Unit texttext-xs uppercase12px, uppercase
Unit colortext-muted-foreground

Backdrop

PropertyTokenValue
Backgroundbg-black/80Black with 80% opacity

Breakpoints

BreakpointUse ForWidth
mdDesktop/TabletWider container (~706px), left-aligned header
smMobileFull-width container (~424px), centered header

Responsive Behavior

  • Drawer slides up from bottom on all screen sizes
  • On mobile (sm), takes full width with small margins, header text centers
  • On desktop (md), centered with max-width constraint, header text left-aligned

Content Variants

VariantUse For
StatisticDisplaying/adjusting values with +/- controls
FormCollecting user input (profile editing, settings)

States

StateImplementation
OpenSlides up from bottom with backdrop
ClosedSlides down, backdrop fades
DraggingUser can swipe down to close

Increment Button States

StateImplementation
Defaultborder border-input bg-input shadow-xs
Hoverbg-accent
Focusring-2 ring-ring ring-offset-2
Pressedbg-accent scale-95

Common Patterns

Statistic Drawer

<Drawer>
  <DrawerTrigger asChild>
    <Button>Set Goal</Button>
  </DrawerTrigger>
  <DrawerContent>
    <DrawerHeader>
      <DrawerTitle>Move Goal</DrawerTitle>
      <DrawerDescription>Set your daily activity goal.</DrawerDescription>
    </DrawerHeader>
    <div className="flex items-center justify-center gap-4 py-4">
      <Button variant="outline" size="icon" onClick={decrement}>
        <Minus className="h-4 w-4" />
      </Button>
      <span className="text-7xl font-bold">{goal}</span>
      <Button variant="outline" size="icon" onClick={increment}>
        <Plus className="h-4 w-4" />
      </Button>
    </div>
    <DrawerFooter>
      <Button>Submit</Button>
      <DrawerClose asChild>
        <Button variant="outline">Cancel</Button>
      </DrawerClose>
    </DrawerFooter>
  </DrawerContent>
</Drawer>

Form Drawer

<DrawerContent>
  <DrawerHeader>
    <DrawerTitle>Edit profile</DrawerTitle>
    <DrawerDescription>Make changes to your profile here.</DrawerDescription>
  </DrawerHeader>
  <div className="space-y-4 px-4">
    <Input placeholder="Name" />
    <Input placeholder="Username" />
  </div>
  <DrawerFooter>
    <Button>Save changes</Button>
    <DrawerClose asChild>
      <Button variant="outline">Cancel</Button>
    </DrawerClose>
  </DrawerFooter>
</DrawerContent>

Accessibility

  • Built on Vaul — handles focus trap automatically
  • Focus trapped within drawer when open
  • Keyboard: Escape to close, Tab/Shift+Tab to cycle focus
  • Screen reader: announces as modal dialog
  • Swipe gesture to dismiss on mobile — always provide a close button as alternative

Gotchas

ProblemSolution
Drawer doesn't swipe closedEnsure vaul library is installed (Drawer uses it)
Content too tallAdd max-h-[80vh] overflow-y-auto to content
Background doesn't scaleAdd shouldScaleBackground prop to Drawer
Handle not visibleDrawerContent includes handle by default

See Also


Last updated: February 9, 2026