Drawer from @prepared911/ui/drawer is a panel that slides in from a viewport edge. Drawer owns open state. DrawerPanel is the surface (edge, size, a required title that names it and renders the default header, closeLabel; an optional header replaces the default header with a DrawerHeader, and an optional aria-label replaces the accessible name). DrawerContent, DrawerFooter, and DrawerNav fill it. A shell that swaps several bodies in one panel uses DrawerHost and createDrawerHostStore from @prepared911/ui/blocks/drawer-host. There is no module-global drawer store and no portal node to mount.
Example
A portaled drawer with header, scrollable list body, one expandable section, and a footer—similar to detail panels when reviewing historical (archived) incidents in dispatch.
Props
Name
Type
Default
Description
children
ReactNode
—
{@link DialogTrigger } and {@link DialogContent }, in any order.
defaultOpen
boolean
false
Initial open state when uncontrolled.
finalFocus
PopupFocusTarget
—
Focused after every close. Omit to return focus to the element that was focused before the dialog opened.
initialFocus
PopupFocusTarget
—
Focused right after the dialog opens. Omit for the first control, or an autofocus element; a scroller never takes it unless it is a labelled tab stop.
modal
boolean
true
When true, opens with showModal(): scrim, page inertness, and focus trap. When false, opens with show();
the page stays interactive and an outside press closes with light-dismiss.
Called once per open or close request: trigger press, close press, Escape (close-request), and a
method="dialog" form submit (form-submit). A cancelable request is vetoed with details.cancel().
open
boolean
—
Controlled open state. Pair with {@link DialogProps.onOpenChange}.
Resize
Pass resizable and resizeLabel on DrawerPanel. The width stays on the DrawerSize steps (Small, Medium, Large) and clamps between Small and Large while dragging. There is no free width, minWidth, or maxWidth.
Placement and stacking
placement on DrawerPanel is DrawerPlacement.Viewport (default, fixed to the viewport at --z-overlay), Container (over the nearest positioned ancestor at --z-overlay), or Inline (a flex item beside its siblings). A non-modal drawer opens with show, not in the top layer, so it stays below every top-layer surface: popovers, menus, selects, tooltips, toasts and modal dialogs. A modal drawer opens with showModal in the top layer and ignores placement. An ancestor that creates a stacking context or a containing block confines a non-modal Viewport or Container panel, so mount app-level drawers at the shell root through DrawerHost.
Room for the page
An open drawer publishes its extent on the document as --ds-drawer-reserve-inline-start, --ds-drawer-reserve-inline-end, --ds-drawer-reserve-block-start, and --ds-drawer-reserve-block-end. onSizeChange on DrawerPanel reports the same extent in JavaScript. A layout that must ignore one drawer reads onSizeChange from the panels it cares about.
Drawer navigation
DrawerNav renders fixed segmented navigation between DrawerHeader and DrawerContent. Set collapseUnselectedLabels to keep the active item label open while collapsing inactive items to their icons. Collapsible DrawerNavItem entries must provide both icon and aria-label.
Name
Type
Default
Description
value
string
—
Controlled selected segment value.
defaultValue
string
—
Initial uncontrolled segment value.
onValueChange
(value: string) => void
—
Runs when selection changes.
fullSpan
boolean
false
Expands segments evenly across the nav width.
collapseUnselectedLabels
boolean
false
Collapses inactive labels to their icons.
actions
ReactNode
—
Controls pinned to the right of the nav.
border
boolean
true
Renders the nav bottom border.
mergeWithHeader
boolean
true
Merges compact nav chrome with the preceding header.
dragToScroll
boolean
true
Enables horizontal drag-to-scroll for overflowing items.
Storybook
Interactive examples and edge cases for this component are in Storybook.