VayuUI

Drawer

A sidebar panel that slides in from any edge of the viewport for navigation, forms, or supplementary content.

Installation

npx vayu-ui-cli@latest add drawer

Usage

Drawer Example

Anatomy

import { Drawer } from 'vayu-ui';

<Drawer side="right">
  <Drawer.Trigger asChild>
    <Button>Open</Button>
  </Drawer.Trigger>
  <Drawer.Overlay />
  <Drawer.Content>
    <Drawer.Header>
      <Drawer.Title>Title</Drawer.Title>
      <Drawer.Description>Description goes here.</Drawer.Description>
    </Drawer.Header>
    <div className="py-4">{/* Content */}</div>
    <Drawer.Footer>
      <Drawer.Close asChild>
        <Button>Close</Button>
      </Drawer.Close>
    </Drawer.Footer>
  </Drawer.Content>
</Drawer>;
  • Drawer — Root container. Manages open/close state and provides context to all subcomponents. Supports controlled and uncontrolled usage.
  • Drawer.Trigger — Button that opens the drawer. Supports asChild to merge props onto a custom element.
  • Drawer.Overlay — Backdrop behind the drawer. Clicking it dismisses the drawer by default.
  • Drawer.Content — The slide-in panel with focus trapping and keyboard navigation. Use Drawer.Header for an in-flow close button.
  • Drawer.Header — Container for Drawer.Title and Drawer.Description.
  • Drawer.Title — Accessible heading rendered as an <h2>. Linked to the dialog via aria-labelledby.
  • Drawer.Description — Accessible description rendered as a <p>. Linked to the dialog via aria-describedby.
  • Drawer.Footer — Container for action buttons, pinned to the bottom of the drawer.
  • Drawer.Close — Button that closes the drawer. Supports asChild for custom elements.
  • Drawer.Portal — Portal wrapper for rendering the drawer outside the DOM tree.

Accessibility

  • Keyboard Support:
    • Escape — Closes the drawer.
    • Tab — Moves focus between interactive elements inside the drawer.
    • Shift + Tab — Moves focus backwards through interactive elements.
    • Focus is trapped within the drawer when open (circular tab navigation).
  • ARIA Attributes:
    • role="dialog" on Drawer.Content.
    • aria-modal="true" on Drawer.Content.
    • aria-labelledby on Drawer.Content links to Drawer.Title.
    • aria-describedby on Drawer.Content links to Drawer.Description.
    • aria-expanded on Drawer.Trigger reflects the open/closed state.
    • aria-haspopup="dialog" on Drawer.Trigger.
    • aria-hidden="true" on Drawer.Overlay.
    • Built-in close button has aria-label="Close drawer".
  • Focus Behavior:
    • When the drawer opens, focus moves to the first interactive element inside Drawer.Content.
    • Focus is trapped within the drawer until it closes.
    • When the drawer closes, focus returns to the trigger element.
    • Body scroll is locked while a modal drawer is open.

Screen reader behavior

When a drawer opens, the screen reader announces the dialog (via role="dialog" and aria-modal="true") and reads the title (linked by aria-labelledby) and description (linked by aria-describedby). The overlay is hidden from the assistive technology tree (aria-hidden="true"). While the drawer is open, only content inside it is accessible to screen readers. When the drawer closes, focus returns to the trigger button and the previously visible content becomes accessible again.

Component Folder Structure

Drawer/
├── Drawer.tsx             # Root component with context provider and state management
├── DrawerTrigger.tsx      # Trigger button that opens the drawer
├── DrawerOverlay.tsx      # Modal backdrop with dismiss on click
├── DrawerContent.tsx      # Slide-in panel with focus trapping and keyboard navigation
├── DrawerHeader.tsx       # Container for title and description
├── DrawerFooter.tsx       # Container for action buttons
├── DrawerTitle.tsx        # Accessible heading linked to dialog
├── DrawerDescription.tsx  # Accessible description linked to dialog
├── DrawerClose.tsx        # Button that closes the drawer
├── DrawerPortal.tsx       # Portal wrapper for DOM rendering
├── types.ts               # TypeScript type definitions
├── index.ts               # Re-exports all components and types
└── README.md              # Component usage reference

Props

Drawer (Root)

PropTypeDefaultDescription
openboolean—Controlled open state.
onOpenChange(open: boolean) => void—Callback when open state changes.
defaultOpenbooleanfalseDefault open state for uncontrolled usage.
side"left" | "right" | "top" | "bottom""right"Direction the drawer slides in from.
modalbooleantrueWhether to lock body scroll and show overlay.
childrenReactNode—Drawer subcomponents.
classNamestring—Additional CSS classes.

Drawer.Trigger

PropTypeDefaultDescription
asChildbooleanfalseMerge props onto child element instead of rendering a button.
classNamestring—Additional CSS classes.

Drawer.Overlay

PropTypeDefaultDescription
dismissiblebooleantrueWhether clicking the overlay closes the drawer.
classNamestring—Additional CSS classes.

Drawer.Content

PropTypeDefaultDescription
trapFocusbooleantrueWhether to trap focus within the drawer when open.
classNamestring—Additional CSS classes.

Drawer.Header

PropTypeDefaultDescription
classNamestring—Additional CSS classes.

Drawer.Footer

PropTypeDefaultDescription
classNamestring—Additional CSS classes.

Drawer.Title

PropTypeDefaultDescription
classNamestring—Additional CSS classes.

Drawer.Description

PropTypeDefaultDescription
classNamestring—Additional CSS classes.

Drawer.Close

PropTypeDefaultDescription
asChildbooleanfalseMerge props onto child element instead of rendering a button.
classNamestring—Additional CSS classes.

Drawer.Portal

PropTypeDefaultDescription
childrenReactNode—Content to render inside the portal.

Header close button

Drawer.Header includes an accessible X control in the header flex row. The old absolutely positioned close button in Drawer.Content has moved into the header. Include a header or compose Drawer.Close explicitly when using a custom layout.

<Drawer.Content>
  <Drawer.Header closeLabel="Close navigation">
    <Drawer.Title>Navigation</Drawer.Title>
    <Drawer.Description>Choose a destination.</Drawer.Description>
  </Drawer.Header>
</Drawer.Content>

Use showClose={false} to hide the default header control. Drawer.Close asChild preserves the child's click handler and respects event.preventDefault().

On this page