Skip to content

Drawer

A panel that slides in from one of the four screen edges. Use Drawer for filters, secondary navigation, or detail views that should overlay the main canvas without fully blocking it. In modal mode it traps focus and renders a backdrop; in non-modal mode it leaves the rest of the page interactive — useful for persistent inspectors next to a 3D viewport.

Live Preview

When to use

  • Drawer — secondary surface anchored to an edge. Holds filters, navigation, or details for the current selection. May or may not block the page.
  • Dialog — modal popup centred on the screen. Demands a response before the user continues.
  • Popover — small floating panel anchored to a trigger. For inline controls and short forms.

Reach for a drawer when the content is meaningful on its own (a filter set, a detail panel) and benefits from a stable edge anchor rather than a centred overlay.

Import

import { Drawer } from 'entangle-ui';

The compound members are attached to Drawer:

<Drawer>
<Drawer.Header>...</Drawer.Header>
<Drawer.Body>...</Drawer.Body>
<Drawer.Footer>...</Drawer.Footer>
</Drawer>

Usage

Drawer is fully controlled. Hold open in the parent and pass onClose to react to the overlay click, the Escape key, or the built-in close button.

const [open, setOpen] = useState(false);
<Button onClick={() => setOpen(true)}>Open Filters</Button>
<Drawer open={open} onClose={() => setOpen(false)} anchor="right" size="md">
<Drawer.Header description="Refine the list below.">
Filters
</Drawer.Header>
<Drawer.Body>...</Drawer.Body>
<Drawer.Footer>
<Button onClick={() => setOpen(false)}>Cancel</Button>
<Button variant="filled" onClick={apply}>Apply</Button>
</Drawer.Footer>
</Drawer>;

Anchors

The anchor prop chooses the edge the panel slides in from.

Anchors

AnchorSlides fromTypical use
leftthe leftPrimary navigation, file trees
rightthe rightFilters, detail views, inspectors
topthe topNotifications, command bars
bottomthe bottomAction sheets on small viewports
<Drawer open={open} onClose={close} anchor="left">...</Drawer>
<Drawer open={open} onClose={close} anchor="right">...</Drawer>
<Drawer open={open} onClose={close} anchor="top">...</Drawer>
<Drawer open={open} onClose={close} anchor="bottom">...</Drawer>

For left and right, size controls the panel width; for top and bottom it controls the height.

Sizes

Use a preset (sm / md / lg / xl) or pass any CSS length — numbers are treated as px, strings pass through.

Sizes

<Drawer open={open} onClose={close} size="sm" />
<Drawer open={open} onClose={close} size="lg" />
<Drawer open={open} onClose={close} size={480} />
<Drawer open={open} onClose={close} size="60vw" />

The panel is capped at 100vw / 100vh so an oversized value will never push past the viewport.

The default modal mode renders a backdrop, traps focus inside the drawer, and blocks interactions with the page behind. Pass modal={false} for a persistent side panel that lets the user keep working in the main canvas.

Non-modal

<Drawer open={open} onClose={close} modal={false}>
<Drawer.Header>Inspector</Drawer.Header>
<Drawer.Body>...</Drawer.Body>
</Drawer>

Non-modal drawers drop the backdrop and the focus trap by default. Re-enable the trap with trapFocus if a particular non-modal flow needs it.

Close behaviour

By default the drawer closes when the user:

  • clicks the overlay (modal only),
  • presses Escape,
  • clicks the close button rendered by Drawer.Header (or any Drawer.CloseButton).

Disable any of these individually:

<Drawer
open={open}
onClose={close}
closeOnOverlayClick={false}
closeOnEscape={false}
>
<Drawer.Header showClose={false}>Confirmation required</Drawer.Header>
...
</Drawer>

Compound API

Mix and match Drawer.Header, Drawer.Body, Drawer.Footer, and Drawer.CloseButton to compose the panel. The members share context with the root and automatically wire up close handling.

Compound members

<Drawer open={open} onClose={close} anchor="right">
<Drawer.Header description="Adjust scene-wide settings.">
Scene Settings
</Drawer.Header>
<Drawer.Body>...</Drawer.Body>
<Drawer.Footer align="space-between">
<Drawer.CloseButton>Reset</Drawer.CloseButton>
<div>
<Button onClick={close}>Cancel</Button>
<Button variant="filled" onClick={save}>
Save
</Button>
</div>
</Drawer.Footer>
</Drawer>

Drawer.CloseButton reads onClose from context — drop it anywhere inside the drawer to add an extra dismiss trigger.

Initial focus

The drawer focuses the first focusable element inside the panel on open. Override this by passing a ref to initialFocusRef:

const inputRef = useRef(null);
<Drawer open={open} onClose={close} initialFocusRef={inputRef}>
<Drawer.Body>
<Input ref={inputRef} placeholder="Search…" />
</Drawer.Body>
</Drawer>;

Portal

By default the drawer renders into document.body. Disable for tests or to keep the drawer inside the local DOM hierarchy:

<Drawer open={open} onClose={close} portal={false}>
...
</Drawer>

Accessibility

  • Renders with role="dialog" and aria-modal="true" (modal mode).
  • aria-labelledby references the heading rendered by Drawer.Header (or the title prop).
  • aria-describedby references the description when provided.
  • Focus is trapped inside the panel when modal; the first focusable element receives focus on open.
  • Escape closes the drawer unless closeOnEscape={false}.
  • Slide-in/out animations are suppressed when the user has prefers-reduced-motion: reduce.
  • The default close button has aria-label="Close drawer".

API Reference

<Drawer>

Prop Type Default Description
open boolean Whether the drawer is open.
onClose () => void Called when the drawer requests close (overlay click, Escape, close button).
anchor 'left' | 'right' | 'top' | 'bottom' 'right' Edge from which the drawer enters.
size 'sm' | 'md' | 'lg' | 'xl' | number | string 'md' Drawer size on the cross axis. Strings like `sm` use presets, number → px, string → CSS value.
modal boolean true Modal mode renders a backdrop and traps focus.
closeOnOverlayClick boolean true Close when the overlay is clicked. Only applies when modal.
closeOnEscape boolean true Close on the Escape key.
trapFocus boolean Trap focus inside the drawer. Defaults to `modal`.
initialFocusRef RefObject<HTMLElement | null> Element to focus on open.
portal boolean true Render in a portal on `document.body`.
title string Used as the `aria-labelledby` reference. Pass as a string or use `Drawer.Header`.
description string Used as the `aria-describedby` reference.
children ReactNode Drawer content (typically `Drawer.Header` / `Drawer.Body` / `Drawer.Footer`).

<Drawer.Header>

Prop Type Default Description
children ReactNode Title content.
description string Optional description rendered below the title.
showClose boolean true Whether to render a close button on the right.

<Drawer.Body>

Prop Type Default Description
children ReactNode Body content.

<Drawer.Footer>

Prop Type Default Description
children ReactNode Footer content (typically action buttons).
align 'left' | 'center' | 'right' | 'space-between' 'right' Horizontal alignment of footer content.

<Drawer.CloseButton>

Prop Type Default Description
children ReactNode Custom button content; defaults to an X icon.
aria-label string 'Close drawer' Override the aria-label.