useDrawer
Hook that manages slide-in panel overlays with focus trap, scroll lock, and dismissable backdrop.
Usage#
import { useMotion } from '@primereact/core/motion';
import { useDrawer } from '@primereact/headless/drawer';
import { usePortal } from '@primereact/headless/portal';
import * as React from 'react';
import { createPortal } from 'react-dom';const { rootProps, triggerProps, backdropProps, popupProps, closeProps, headerProps, state } = useDrawer();
const portal = usePortal();
<div {...rootProps}>
<button {...triggerProps}></button>
{portal.state.mounted &&
createPortal(
<>
<div {...backdropProps} />
<div {...popupProps}>
<div {...headerProps}>
<button {...closeProps}></button>
</div>
...
</div>
</>,
document.bodyuseDrawer manages open/close state, focus trapping, scroll locking, and dismissable backdrop behavior. See Primitive for a component-based API.
Features#
- Open/close lifecycle — controlled or uncontrolled visibility with dismissable backdrop and escape handling
- Focus management — focus trap inside the panel while open, with automatic return-focus to the trigger on close
- Positioning — slides in from left, right, top, or bottom, exposed as a
data-positionattribute on the root - Scroll and layering — body scroll lock plus
baseZIndex/autoZIndexto stack with other overlays - Portal-ready — designed to pair with
usePortalandcreatePortalfor SSR-safe body portaling - Imperative controls —
close()method andstate.openflag for programmatic control and conditional rendering
Working with callbacks#
Controlled open state#
Pass open and onOpenChange to drive visibility from external state.
const [isOpen, setIsOpen] = React.useState(false);
const drawer = useDrawer({
open: isOpen,
onOpenChange: (e) => setIsOpen(e.value)
});Animated transitions#
Combine with useMotion so the drawer and backdrop stay mounted through their exit transition before unmounting.
const drawer = useDrawer();
const motion = useMotion({ open: drawer.state.open });
{
motion.present && (
<div {...drawer.popupProps} ref={motion.ref}>
...
</div>
);
}Non-dismissable drawer#
Force an explicit close action by disabling backdrop dismissal — useful during unsaved work.
const drawer = useDrawer({ dismissable: false });Stacking with other overlays#
Use autoZIndex with a baseZIndex when the drawer must layer above dialogs or dropdowns.
const drawer = useDrawer({ baseZIndex: 1100, autoZIndex: true });Styling with data attributes#
The hook exposes state through data-* attributes on each part. Use them as CSS selectors — no className juggling.
| Scope | Part | States |
|---|---|---|
drawer | root | data-open, data-closed, data-position |
drawer | popup | data-open, data-closed |
[data-scope='drawer'][data-part='root'][data-open] {
opacity: 1;
}
[data-scope='drawer'][data-part='root'][data-closed] {
opacity: 0;
}
[data-scope='drawer'][data-part='root'][data-position='right'] {
right: 0;
left: auto;
}API#
useDrawer#
| Name | Type | Default |
|---|---|---|
open | boolean | — |
| Specifies the visibility of the drawer. | ||
defaultOpen | boolean | — |
| Specifies the default visibility of the drawer. | ||
blockScroll | boolean | false |
| Whether to block scrolling when the drawer is open. | ||
dismissable | boolean | true |
| Whether clicking outside closes the drawer. | ||
baseZIndex | number | 0 |
| Base zIndex value to use in layering. | ||
autoZIndex | boolean | true |
| Whether to automatically manage layering. | ||
trapped | boolean | true |
| When enabled, focus is trapped within the drawer (modal behavior). | ||
position | "top" | "bottom" | "left" | "right" | "full" | left |
| Position of the drawer. | ||
onOpenChange | (event: useDrawerChangeEvent) => void | — |
| Callback function that is called when the trigger is clicked. | ||
onExitComplete | () => void | — |
| Callback fired after the leave (exit) transition completes. | ||
Accessibility#
Escape closes the drawer, focus is trapped inside while open, and focus returns to the trigger on dismiss. See Primitive for full WAI-ARIA compliance details.