vaiperui

components / drawer

Drawer

Side panel that slides from any edge, with the same dialog behavior as Modal.

How to use

Same accessibility as `Modal` (focus trap, scroll lock, Escape, backdrop). `side` picks the edge: left/right panels are up to 400px wide, top/bottom ones span the width up to 80% of the height. Use `DrawerClose` around a button to close from inside.

preview
Drawer.tsx
import { Button, Drawer, DrawerClose } from "vaiperui";
<Drawer
trigger={<Button>Open</Button>}
side="right"
title="Filters"
footer={<DrawerClose><Button>Apply</Button></DrawerClose>}
>
Drawer content
</Drawer>

Sides

preview
top
right
bottom
left
sides.tsx
<Drawer side="top" title="top" trigger={<Button>top</Button>}>...</Drawer>
<Drawer side="right" title="right" trigger={<Button>right</Button>}>...</Drawer>
<Drawer side="bottom" title="bottom" trigger={<Button>bottom</Button>}>...</Drawer>
<Drawer side="left" title="left" trigger={<Button>left</Button>}>...</Drawer>

Props

PropValuesDefaultDescription
titleReactNoderequiredHeading and accessible name (aria-labelledby)
triggerReactElementnoneElement that opens it; not needed when controlled
open / defaultOpen / onOpenChangeboolean / boolean / (open) => voidnoneControlled or uncontrolled open state
showClose / closeLabelboolean / stringtrue / "Close"× button in the header and its accessible name
closeOnEscape / closeOnBackdropClickbooleantrueWhether Escape or a backdrop click closes it
sideleft | right | top | bottomrightEdge where the panel sits
animationnone | smooth | fast | bouncesmoothSlide-in transition

Accessibility

  • The panel is rendered in a portal on `document.body` with `role="dialog"` and `aria-modal="true"`.
  • `title` is rendered as an `h2` and linked through `aria-labelledby`; the body is linked through `aria-describedby`.
  • On open, focus moves to the first focusable element in the panel (or the panel itself) and returns to the previously focused element on close, if it is still in the DOM.
  • Tab and Shift+Tab are trapped inside the panel while it is open, and the body scroll is locked.
  • The trigger gets `aria-haspopup="dialog"` and `aria-expanded`.
  • The close button has `aria-label` from `closeLabel` (default "Close"); the backdrop is `aria-hidden`.

Keyboard

KeyAction
Tab / Shift+TabMoves focus between elements inside the panel, wrapping at the ends
EscCloses it (disabled with `closeOnEscape={false}`)

Keep in mind

  • `title` is required and must describe the dialog; translate `closeLabel` since its default is English.
  • The page behind is not marked `inert` or `aria-hidden`; it relies on `aria-modal` and the Tab trap. Clicking the backdrop closes it unless `closeOnBackdropClick={false}`.