Skip to content

Drawer

OverlaysSince 0.2.0
import { Drawer } from '@absoluteui/components/drawer';
import '@absoluteui/components/drawer/style.css';

Using Angular? See Drawer for Angular.

A panel that slides in from a screen edge, built on native <dialog>.

  • Modal (default): showModal() gives top-layer rendering, an inert background, a backdrop, and native focus containment. AbsoluteUI adds scroll lock and focus restoration.
  • Non-modal (modal: false): show() keeps the page interactive. Focus moves into the drawer on open, and Escape closes it when it is the topmost overlay.

Both modes share Dialog’s close reasons and cancelable close events. Use non-modal drawers only for supplementary panels, such as filters or record details next to live content.

  • Secondary tasks that keep page context visible, such as editing a record from a list.
  • Navigation or settings panels on small screens (modal).
  • Supplementary panels such as filters or details next to live content (non-modal).
  • Short confirmations or destructive prompts: use Dialog (role=alertdialog).
  • Primary page content or long multi-step flows: use a page.
  • Content anchored to a trigger: use a popover or menu.
  • Non-modal drawers for tasks that must be completed before continuing: use a modal drawer.
import { Drawer, type DrawerCloseDetail } from '@absoluteui/components/drawer';
const editor = Drawer.create({
title: 'Edit customer',
description: 'Changes are saved when you select Save.',
size: 'lg',
actions: [
{ label: 'Cancel', value: 'cancel' },
{ label: 'Save', value: 'save', variant: 'primary' },
],
});
editor.element.addEventListener('aui-close', (event) => {
const { returnValue } = (event as CustomEvent<DrawerCloseDetail>).detail;
if (returnValue === 'save') {
// persist changes
}
});
editor.open();
import { Drawer } from '@absoluteui/components/drawer';
const filters = Drawer.create({ title: 'Filters', modal: false, placement: 'start', size: 'sm' });
const list = document.createElement('fieldset');
filters.body.append(list);
document.querySelector('#toggle-filters')?.addEventListener('click', () => {
if (filters.isOpen) filters.close();
else filters.open();
});
import { Drawer } from '@absoluteui/components/drawer';
let dirty = true;
const panel = Drawer.create({ title: 'Edit profile', placement: 'bottom' });
panel.element.addEventListener('aui-cancel', (event) => {
if (dirty) event.preventDefault();
});
panel.open();
dirty = false;
import { Drawer } from '@absoluteui/components/drawer';
import '@absoluteui/components/button/style.css';
import '@absoluteui/components/drawer/style.css';
const filters = Drawer.create({ title: 'Filters', modal: false, placement: 'start', size: 'sm' });
document.querySelector('#toggle-filters')?.addEventListener('click', () => {
if (filters.isOpen) filters.close();
else filters.open();
});
import { Drawer } from '@absoluteui/components/drawer';
const editor = Drawer.create({
title: 'Edit customer',
size: 'lg',
actions: [
{ label: 'Cancel', value: 'cancel' },
{ label: 'Save', value: 'save', variant: 'primary' },
],
});
editor.open();
<dialog class="aui-drawer aui-drawer--{modal|non-modal} aui-drawer--{placement} aui-drawer--{size}"
data-aui-side="left|right|top|bottom" dir aria-labelledby aria-describedby>
<header class="aui-drawer__header">
<h2 class="aui-drawer__title">title</h2>
<button class="aui-button aui-drawer__close" aria-label="Close">icon</button>
</header>
<p class="aui-drawer__description">description</p>
<div class="aui-drawer__body">content</div>
<footer class="aui-drawer__footer">action buttons</footer>
</dialog>
Drawer.create(options: DrawerOptions, doc?: Document): Drawer

Global configuration key: drawer (see Configuration).

Option Type Default Description
title string Accessible name, rendered as the heading. Required.
description string Referenced by aria-describedby.
content Node | string Body content; strings render as text.
actions readonly DrawerAction[] Footer buttons; clicking closes with the action value.
modal boolean true true: showModal() with inert background, backdrop and scroll lock. false: show(); background stays interactive (supplementary panels only). Set at creation only.
icons Partial<{ close: IconRef }> Icon slot overrides.
placement 'start' | 'end' | 'top' | 'bottom' "end" Screen edge; start/end follow the writing direction. Globally configurable.
size 'sm' | 'md' | 'lg' "md" Inline size for side drawers; maximum block size (capped at 85dvh) for top/bottom. Globally configurable.
closeOnEscape boolean true Escape requests close (cancelable) when the drawer is the topmost overlay. Globally configurable.
closeOnBackdrop boolean true Backdrop click requests close (cancelable). Modal only. Globally configurable.
showCloseButton boolean true Render the header close button. Globally configurable.
direction 'ltr' | 'rtl' "ltr" Maps start/end placement to a physical edge; applied as the element’s dir. Globally configurable. Inherited from common configuration.
Property Type Description
element HTMLDialogElement The native dialog element.
body HTMLDivElement Consumer content area.
isOpen boolean Whether the drawer is open.
modal boolean Whether the drawer was created modal.
Method Description
open(): void Open (modal: showModal() and scroll lock; non-modal: show()); moves focus inside and captures focus for restoration.
close(returnValue?: string): void Close programmatically (not cancelable).
update(options: DrawerUpdateOptions): void Merge and apply options (all except modal).
destroy(): void Close if open, remove element, listeners, overlay registration, subscriptions.
Event Detail Bubbles Cancelable Description
aui-open null No No After the drawer opened.
aui-cancel { returnValue: string; reason: 'escape' | 'backdrop' | 'close-button' | 'action' } No Yes Before a user-initiated close; preventDefault keeps it open.
aui-close { returnValue: string; reason: DrawerCloseReason } No No After the drawer closed.
Area Description
body Drawer.body (or content option).

All rules live in the aui.components cascade layer, so application styles override them without specificity battles.

Class Purpose
aui-drawer Native <dialog> element; data-aui-side holds the physical edge (left | right | top | bottom).
aui-drawer--modal | --non-modal Modality modifiers.
aui-drawer--start | --end | --top | --bottom Logical placement modifiers.
aui-drawer--sm | --md | --lg Size modifiers.
aui-drawer__header Title row with close button.
aui-drawer__title Heading (h2).
aui-drawer__close Close button.
aui-drawer__description Description paragraph.
aui-drawer__body Scrollable content area.
aui-drawer__footer Action buttons.
Slot Default icon
close close

Tokens this stylesheet reads (verified in CI). Override them in a theme or with the Theme Designer.

--aui-border-width | --aui-color-backdrop | --aui-color-border | --aui-color-focus-ring | --aui-color-surface | --aui-color-text | --aui-color-text-muted | --aui-drawer-width-lg | --aui-drawer-width-md | --aui-drawer-width-sm | --aui-duration-fast | --aui-duration-normal | --aui-easing-standard | --aui-focus-ring-offset | --aui-focus-ring-width | --aui-font-family-sans | --aui-font-size-lg | --aui-font-size-md | --aui-font-size-sm | --aui-font-weight-semibold | --aui-line-height-normal | --aui-line-height-tight | --aui-shadow-lg | --aui-space-1 | --aui-space-2 | --aui-space-3 | --aui-space-5

type DrawerPlacement = 'start' | 'end' | 'top' | 'bottom';
type DrawerSide = 'left' | 'right' | 'top' | 'bottom';
type DrawerCloseReason = 'escape' | 'backdrop' | 'close-button' | 'action' | 'programmatic';
interface DrawerAction { label: string; value: string; variant?: ButtonVariant; autofocus?: boolean }
interface DrawerCloseDetail { returnValue: string; reason: DrawerCloseReason }
interface DrawerOptions {
title: string;
description?: string;
content?: Node | string;
actions?: readonly DrawerAction[];
modal?: boolean;
direction?: 'ltr' | 'rtl';
icons?: Partial<{ close: IconRef }>;
placement?: DrawerPlacement;
size?: 'sm' | 'md' | 'lg';
closeOnEscape?: boolean;
closeOnBackdrop?: boolean;
showCloseButton?: boolean;
}
type DrawerUpdateOptions = Partial<Omit<DrawerOptions, 'modal'>>;

Pattern: WAI-ARIA APG Dialog (Modal) pattern implemented with native <dialog>.showModal(); non-modal drawers use <dialog>.show() as a non-modal dialog.

Key Context Action
Tab / Shift+Tab Modal drawer open Moves focus among drawer controls; the background is inert (native modal).
Tab / Shift+Tab Non-modal drawer open Follows document order; focus can leave the drawer and reach the page.
Escape Drawer is the topmost overlay, closeOnEscape=true Emits cancelable aui-cancel, then closes and restores focus.
Escape closeOnEscape=false No effect; the native close request is prevented, including repeated presses.
Escape Nested overlay (e.g. combobox listbox) open Closes only the topmost overlay.
Enter / Space Action or close button focused Activates the button.
Element Role
<dialog> dialog (native)
title heading level 2
Attribute Element Value
aria-labelledby <dialog> id of the title heading
aria-describedby <dialog> id of the description (when provided)
aria-label close button Localized ‘Close’
aria-modal <dialog> Implicit true for modal drawers (native showModal()); absent for non-modal drawers

On open, focus moves to the autofocus action or first focusable element. On close, a modal drawer returns focus to the element focused before opening; a non-modal drawer does so only when focus was inside the drawer, so users working in the page keep their place.

Criterion Level Note
1.3.1 Info and Relationships A
2.1.1 Keyboard A
2.1.2 No Keyboard Trap A
2.4.3 Focus Order A
2.4.7 Focus Visible AA
4.1.2 Name, Role, Value A

Automated test results: Accessibility and compliance.

  • Background inertness and top-layer rendering of modal drawers are provided by the browser.
  • Slide motion is removed under prefers-reduced-motion and data-aui-animation=‘none’.
  • Use non-modal drawers only for supplementary panels (filters, details).
  • Manual screen-reader verification (NVDA, JAWS, VoiceOver) is pending.

closed, opening (slide-in), open, closing (an aui-cancel listener is pending), and closing (slide-out).

From Trigger To
closed open() open (focus moves inside; modal: scroll locked, background inert)
open Escape / backdrop (modal) / close button / action aui-cancel -> closed, unless prevented
open close() closed (no aui-cancel)
open consumer <form method="dialog"> submit closed (reason action)
  • Initial focus goes to the action with autofocus: true, otherwise the first focusable element (native dialog focusing steps).
  • A modal drawer returns focus to the element that was focused before open().
  • A non-modal drawer returns focus only when focus was inside the drawer at close, so users working in the page keep their place.
  • Non-modal drawers do not trap focus. Keep them supplementary, and give users a visible way to reopen and close them.
  • Slide motion is removed under prefers-reduced-motion and data-aui-animation='none'.
  • Manual screen-reader verification is pending.

Side drawers use min(drawer-width token, 100vw) and the full viewport height. Top and bottom drawers use the full inline size and max-block-size: min(drawer-width token, 85dvh). The body scrolls. Below 480px, side drawers take 100vw and footer actions stack full-width.

start and end resolve against the configured direction: start is the left edge in LTR and the right edge in RTL. The resolved edge is exposed as data-aui-side, the slide direction follows it, and the element receives dir. Inner layout uses logical properties; the close button sits at the inline end.

Token-driven surface, border, shadow, and backdrop. Only the inner edge has a border. Forced-colors mode uses a CanvasText border.

configureAbsoluteUI({ components: { drawer: { placement: 'start', size: 'lg', closeOnBackdrop: false } } });

Changes apply to existing instances immediately.

Instance options override global and theme defaults. All configurable options are dynamic; modal is init-only.

icons.close (default close), resolved through the global icon provider/registry.

Title, description, and string content use textContent. No HTML parsing, no inline styles. CSP-safe.

No persistence, network access, or telemetry.

Constant work per open/close: one showModal()/show() call, one overlay-stack entry, one scroll-lock reference (modal), and one focus restore point. Budget: open() under 16 ms in headless Chromium.

Not data-driven. Body content size is limited only by the consumer; the body scrolls.

  • closeOnEscape: false also blocks repeated Escape presses. Chrome would otherwise let the second Escape through without user activation.
  • A non-modal drawer yields Escape to a modal dialog opened elsewhere, because it is inert behind that dialog.
  • Escape closes a non-modal drawer when it is the topmost overlay, even if focus is in the page.
  • Destroying an open drawer releases the scroll lock and overlay registration, and restores focus.
Code Category Cause
DRAWER_TITLE configuration Missing or blank title.
DRAWER_OPTIONS configuration Invalid modal or actions.
CFG_INVALID_VALUE / CFG_UNKNOWN_OPTION configuration Invalid or unknown configuration, such as an unknown placement.

A failed construction or update leaves no rendering, listeners, overlay registration, or subscriptions behind, and a failed update() keeps the previous options.

  • Non-modal drawers are not in the top layer. They use z-index: 1000, so page content with a higher stacking context can cover them.
  • The element’s dir follows the configured direction (default 'ltr'), not the document’s dir attribute.
  • Slide animations need @starting-style and transition-behavior: allow-discrete. Older browsers open and close without animation.
  • Manual screen-reader verification is pending.
  • Switching modal after creation.
  • Swipe-to-dismiss gestures.
  • HTML strings as content: pass a Node instead.