Overlay

Drawer

Edge-mounted overlay panel that slides in from left or right. Built on Radix Dialog with focus trapping, scroll lock, backdrop dismiss, and directional slide-in/slide-out animations. Use for temporary contextual panels — unlike Sidebar (in-flow layout), Drawer overlays content and dismisses on outside click.

// Radix — @radix-ui/react-dialog
import { Drawer } from "@shavin/ui";

Variants

Two presentation variants — floating is the default. Both slide in from the right.

Two presentation variants — floating is the default. Both slide in from the right.
import { Drawer } from "@shavin/ui";

// Floating (default) — inset card with margin, radius, shadow
<Drawer side="right" title="Filters" open={open} onClose={() => setOpen(false)}>
  Content
</Drawer>

// Fixed — flush against the edge
<Drawer side="right" variant="fixed" title="Filters" open={open} onClose={() => setOpen(false)}>
  Content
</Drawer>

Render sides

Drawers can mount from the left or right edge. Both use the floating variant.

Drawers can mount from the left or right edge. Both use the floating variant.
import { Drawer } from "@shavin/ui";

// Right side (default)
<Drawer side="right" title="Quick settings" open={open} onClose={() => setOpen(false)}>
  Content
</Drawer>

// Left side
<Drawer side="left" title="Navigation" open={open} onClose={() => setOpen(false)}>
  Content
</Drawer>

Utilities

Footer action slot and non-dismissible mode for requiring explicit interaction.

Footer action slot and non-dismissible mode for requiring explicit interaction.
import { Drawer, Button } from "@shavin/ui";

// With footer action
<Drawer
  side="right"
  size="lg"
  title="Inspector"
  subtitle="Object properties"
  open={open}
  onClose={() => setOpen(false)}
  footer={<Button variant="solid" tone="accent">Apply changes</Button>}
>
  Content
</Drawer>

// Non-dismissible — backdrop click disabled
<Drawer
  side="right"
  title="Required action"
  dismissible={false}
  open={open}
  onClose={() => setOpen(false)}
  footer={<Button variant="solid" tone="accent">Acknowledge</Button>}
>
  Content
</Drawer>

Props

PropTypeDefaultDescription
open*boolean—Whether the drawer is open.
onClose*() => void—Callback fired when drawer requests to close (Escape, backdrop click, close button).
title*ReactNode—Drawer header title (string or custom content like a logo).
subtitlestring—Optional subtitle line below title.
side"left" | "right"rightEdge the drawer slides in from. Default "right".
variant"fixed" | "floating""floating"Presentation variant. "floating" = inset card (default), "fixed" = flush edge.
size"sm" | "md" | "lg" | "xl"mdPanel max-width. sm (384px), md (448px), lg (512px), xl (672px). Default "md".
dismissiblebooleantrueWhether clicking the backdrop closes the drawer. Default true.
overlayOpacitynumberusesBackdrop opacity intensity 0–1. Default uses the theme's --overlay-opacity token.
childrenReactNode—Drawer body content.
footerReactNode—Footer slot rendered at the bottom (e.g. action buttons).
classNamestring—Additional className overrides for the panel.
headerClassNamestring—Additional className overrides for the header container.
modalbooleantrueWhether to use modal mode (focus trap + pointer event blocking). Default true. Set false to allow nested popovers/menus.

* required

Keyboard

KeyAction
TabMoves focus to the next focusable element (trapped within the drawer).
Shift + TabMoves focus to the previous focusable element.
EscapeCloses the drawer with slide-out animation and returns focus to the trigger.

Data attributes

AttributeValues
[data-state]"open" | "closed" — on the overlay and content while mounted.
[data-side]"left" | "right" — on the content element, reflects the mounted edge.
[data-variant]"floating" | "fixed" — on the content element, reflects the presentation variant.

Best Practices

[Do]Use Drawer for temporary contextual panels like filters, inspectors, quick settings, or mobile navigation that should overlay content and dismiss on outside interaction.
[Don't]Do not use Drawer for persistent navigation or always-visible panels — use Sidebar (in-flow layout) instead. Drawer is for temporary, dismissible surfaces.

Drawer vs Sidebar

Use Drawer when

  • • Temporary filter panels
  • • Contextual inspectors
  • • Quick settings that don't need to persist
  • • Mobile navigation menus
  • • Any panel that should dismiss on outside click

Use Sidebar when

  • • Persistent app navigation
  • • Always-visible configuration panels
  • • Dashboards with fixed layout columns
  • • Panels that should push content, not overlay
  • • When the panel must reserve layout space
Configure Theme
Theme
Accent Color
Custom
Gray Family
Appearance
Radius
Scaling
Panel Style
Heading Font
Body Font