oks-ui

How to use

Drawer

Drawer is a slide-in panel from any edge of the viewport — filters, notifications, a details view opened from a table row. It's built on the same overlay stack, focus-trapping, and backdrop machinery as Modal, so the two behave consistently and stack correctly together; the difference is purely presentational (an edge-anchored panel instead of a centered box).

import { Drawer, Button } from "oks-ui";

Basic controlled drawer

Same controlled shape as Modal: you own isOpen, onClose fires from every dismissal path (backdrop click, Escape, the header close button).

function NotificationsDrawer() {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <>
      <Button variant="ghost" onPress={() => setIsOpen(true)}>Notifications</Button>
      <Drawer isOpen={isOpen} onClose={() => setIsOpen(false)} title="Notifications" position="right">
        <NotificationsList />
      </Drawer>
    </>
  );
}

Position and sizing

position picks the edge it slides in from (left, right, top, bottom — right is the default). width applies to left/right drawers, height to top/bottom ones; both accept the usual token scale or a raw string/number for an exact size.

<Drawer isOpen={isOpen} onClose={onClose} position="left" width="320px" title="Filters">
  <FilterForm />
</Drawer>

<Drawer isOpen={isOpen} onClose={onClose} position="bottom" height="60%" title="Details">
  <DetailsPanel />
</Drawer>

Header, body, footer

Same three-slot shape as Modal — title for the header, children for the body, actions for the footer. Any of the three can be omitted independently.

<Drawer
  isOpen={isOpen}
  onClose={onClose}
  title="Edit item"
  actions={
    <>
      <Button variant="soft" onPress={onClose}>Cancel</Button>
      <Button color="primary" onPress={handleSave}>Save</Button>
    </>
  }
>
  <ItemForm />
</Drawer>

Stacking with Modal

Because Drawer shares Modal's overlay stack, opening a Drawer from inside an already-open Modal (or vice versa) layers correctly without any manual z-index coordination, and Escape only dismisses the topmost overlay.

Accessibility

  • Traps focus inside the drawer while open and restores it to the triggering element on close, same as Modal.
  • ariaLabel (defaults to "Drawer") names the drawer region for assistive technology; set it to something specific when title alone doesn't describe the drawer's purpose clearly enough on its own.
  • closeOnEscape (on by default) closes the drawer on Escape, respecting the shared overlay stack when multiple overlays are open at once.
  • initialFocusRef sends initial focus somewhere specific on open (a search input at the top of a filters drawer, for instance) instead of the drawer's default first-focusable-element behavior.

Props reference

All available props for Drawer. See the full component page for an interactive playground.

Props

title

ReactNode

Default:

Header title.

children

ReactNode

Default:

Body content.

actions

ReactNode

Default:

Footer action buttons.

Best practices

  • Use position="right" (the default) for supplementary content read alongside the page (details, filters) and position="bottom" for content that reads more like an action sheet, especially on narrower viewports.
  • Keep a Drawer's width/height proportionate to its content — an oversized drawer for a two-field form just wastes space and pushes the actual content further from the edge it slid in from.
  • For content that should block the page entirely and demand a decision, prefer Modal — Drawer's edge-anchored layout reads as supplementary, not as the primary focus of the screen.
  • Set closeOnOutsideClick={false} for a drawer holding an in-progress, hard-to-recreate form (a multi-field filter set) so an accidental outside click doesn't discard it.
View full API reference for Drawer