Drawer
A headless, WCAG-compliant drawer component for React that provides focus trapping, scroll lock, click outside, ESC-to-close, and portal support. Fully customizable — the user controls styles and the close button.
Usage
Basic usage
import { Drawer } from '@nofinite/nui';
<Drawer open={open} onClose={handleClose}>
{/* custom content */}
</Drawer>;
With typical interaction
<Drawer
open={open}
onClose={handleClose}
position="left"
disableEsc={false}
disableClickOutside={false}
>
<div style={{ padding: 20 }}>
<h2>Custom Drawer</h2>
<p>You can style anything in here.</p>
<button onClick={handleClose}>Close</button>
</div>
</Drawer>
Variants
Drawer supports different positions:
<Drawer position="left" />
<Drawer position="right" />
<Drawer position="top" />
<Drawer position="bottom" />
Available variants:
left– Drawer slides in from the left side.right– Drawer slides in from the right side (default).top– Drawer slides down from the top.bottom– Drawer slides up from the bottom.
Guidelines:
- Use
left/rightfor side navigation or menus. - Use
top/bottomfor temporary alerts, modals, or contextual drawers.
Sizes
Sizes are controlled via CSS or custom styling. Width/height varies by position:
left/right: 320px widthtop: 260px heightbottom: 280px height
You can override dimensions with custom styles:
<Drawer position="left" className="custom-width">
{/* content */}
</Drawer>
States
<Drawer open={true} />
<Drawer open={false} />
<Drawer disableEsc={true} />
<Drawer disableClickOutside={true} />
open– whether the drawer is visible.disableEsc– disables closing with the ESC key.disableClickOutside– disables closing by clicking outside the drawer.
Native Props / Composition
Drawer extends div props. You can pass className, style, or any HTML attributes:
<Drawer
open={open}
onClose={handleClose}
className="my-custom-class"
aria-label="Navigation drawer"
>
{/* content */}
</Drawer>
Props
| Prop | Type | Default | Description |
|---|---|---|---|
open | boolean | — | Controls whether the drawer is open. |
onClose | () => void | — | Callback invoked when the drawer requests to close. |
position | "left" | "right" | "top" | "bottom" | right | Determines the drawer slide-in position. |
disableEsc | boolean | false | If true, ESC key will not close the drawer. |
disableClickOutside | boolean | false | If true, clicking outside will not close the drawer. |
className | string | "" | Additional CSS classes for custom styling. |
children | React.ReactNode | — | Content rendered inside the drawer. |
...rest | React.HTMLAttributes<HTMLDivElement> | — | Any other native HTML attributes forwarded to the drawer. |
Behavior Notes
- Focus is trapped inside the drawer while open.
- Background scrolling is locked when drawer is open.
- Clicking outside or pressing ESC closes the drawer (unless disabled).
- Drawer uses a portal to render at the document root.
- Focus is restored to the previously focused element on close.
- Drawer supports inert management for sibling elements to improve accessibility.
<Drawer open={open} onClose={handleClose}>
{/* Example of focus trap and scroll lock in action */}
</Drawer>
Accessibility
- Renders as:
<div role="dialog" aria-modal="true"> - Keyboard support: Tab and Shift+Tab for focus navigation, ESC to close (if not disabled).
- Screen readers are notified of the modal context.
- Proper inert handling ensures background content is not interactable.
- Icon-only or custom close buttons should have accessible labels.
<Drawer aria-label="Navigation drawer">
<button aria-label="Close drawer">X</button>
</Drawer>
Layout
- Fixed overlay covers the viewport.
- Drawer is positioned absolutely relative to overlay based on
position. - Drawer width/height can be customized via CSS or
className. - Overlay ensures modal layering and dimming.
<Drawer open={open} style={{ width: '400px' }}>
Full-width custom drawer
</Drawer>
Best Practices
Do
- Use for side menus, contextual panels, or modal-like overlays.
- Provide a clear, accessible close button inside the drawer.
- Combine with proper focus management for accessibility.
Don’t
- Avoid placing critical content behind a drawer without a clear trigger.
- Don’t overload with unrelated functionality inside a single drawer.
- Don’t rely solely on click outside or ESC; always provide an explicit close mechanism.