Skip to content

Documentation

neelam-ui

Search documentation

Getting Started
Forms
Overlays
Navigation
Data Display
Layout
AI & Chat
Blocks
GitHub repository

Drawer

A panel pinned to one edge of the viewport. A Dialog with different panel styling — everything about opening, closing, and labelling is identical.

Filters

Narrow the results without leaving the page.

Usage#

import {
  Drawer,
  DrawerClose,
  DrawerContent,
  DrawerDescription,
  DrawerHeader,
  DrawerTitle,
  DrawerTrigger,
} from "neelam-ui";
 
<Drawer>
  <DrawerTrigger>Open filters</DrawerTrigger>
  <DrawerContent side="right">
    <DrawerHeader>
      <DrawerTitle>Filters</DrawerTitle>
      <DrawerDescription>Narrow the results.</DrawerDescription>
    </DrawerHeader>
  </DrawerContent>
</Drawer>

Sides#

side picks the edge the panel slides in from — "right" by default.

Slides in from the top

Slides in from the right

Slides in from the bottom

Slides in from the left

left and right are full-height and capped at max-w-sm; top and bottom are full-width and capped at 80vh, so a drawer never quite covers the page it came from.

Built on Dialog#

DrawerContent is a real <dialog> opened with showModal(), so focus trapping, Escape, inert background content, and top-layer stacking all come from the browser rather than being re-implemented. DrawerTrigger, DrawerClose, DrawerHeader, DrawerTitle, DrawerDescription, and DrawerFooter are the same components as their Dialog counterparts under drawer-flavoured names.

Why m-0 matters here

A <dialog> defaults to margin: auto. Left alone, that fights the panel's own inset and h-dvh sizing: with top, height, and bottom all set, the browser resolves the over-constrained box by splitting the leftover space into equal margins — shrinking and centring the drawer instead of pinning it flush to the edge.

Dismissal#

Unlike AlertDialog, a drawer is meant to be easy to leave. All three routes are on by default:

  • closeOnOutsideClick — clicking the backdrop closes it. Pass false to require an explicit choice.
  • hideCloseButton — pass true to drop the corner button, which is otherwise rendered with aria-label="Close".
  • Escape — native <dialog> behaviour.

Keyboard#

Keyboard shortcuts
KeyBehaviour
EnterSpaceOpens the drawer when focus is on DrawerTrigger.
EscapeCloses the drawer. Native <dialog> behaviour.
TabShift+TabCycles focus within the drawer and cannot leave it.

Accessibility#

  • DrawerTitle supplies the accessible name through aria-labelledby and is required — without it, screen reader users get an unnamed modal.
  • DrawerDescription, when present, is wired up with aria-describedby.
  • Focus moves into the drawer on open and returns to the trigger on close.
  • The page behind is inert via the platform, so it cannot be reached by keyboard or virtual cursor while the drawer is open.
  • The slide transition is dropped entirely under prefers-reduced-motion (WCAG 2.3.3); the drawer simply appears.

A drawer is still a modal

It looks lighter than a dialog, but it traps focus and inerts the page just the same. For something the user should be able to work alongside, reach for Popover instead.

API reference#

Drawer#

Props for Drawer
PropTypeDefault
childrenReactNode
defaultOpen

Initial open state when uncontrolled. Defaults to `false`.

boolean
onOpenChange

Called whenever the open state changes, whether from `DialogTrigger`, `DialogClose`, the built-in close button, Escape, or an outside click.

((open: boolean) => void)
open

Controls the open state. Omit to let the dialog manage its own state.

boolean

DrawerContent#

Props for DrawerContent
PropTypeDefault
defaultOpen

Initial open state when uncontrolled. Defaults to `false`.

boolean
onOpenChange

Called whenever the open state changes, whether from `DialogTrigger`, `DialogClose`, the built-in close button, Escape, or an outside click.

((open: boolean) => void)
open

Controls the open state. Omit to let the dialog manage its own state.

boolean