Contact Us

Drawer

DOC-00068 reference implementor, developer

Overview

Drawer is a CORE-OWNED reusable slide-in overlay primitive in src/core/components/primitives/Drawer.astro (D1). It provides a focus-trapped panel with a dismissible backdrop, ESC-to-close, scroll lock, and reduced-motion support.

The consumer owns the state — the parent element provides a boolean variable that controls the drawer’s open/close state. The binding prop tells Drawer which variable to bind to.

Who this is for

  • Implementors using Drawer for mobile menus, filter panels, or side navigation
  • Developers building new overlay-based components on top of the Drawer primitive

Props

PropTypeDefaultDescription
idstringrequiredElement ID for the drawer panel
position"left" | "right" | "full-width""right"Slide-in direction and panel sizing
bindingstring"open"Name of the boolean variable controlling open/close state on the parent element
labelstringAccessible label for the dialog panel (aria-label)
classstringAdditional CSS classes on the drawer panel element

Architecture

Drawer renders two sibling DOM elements:

  1. Backdrop — a fixed overlay covering the full viewport using --st-color-nav-mobile-overlay at 50% alpha with click-to-close behavior. Marked aria-hidden="true" so screen readers ignore it.
  2. Panel — the drawer content area, positioned fixed with overflow-y: auto, overscroll-contain, and --st-color-nav-mobile-bg for the mobile navigation surface.

Both elements are hidden by default and revealed by the vanilla JS controller to prevent a flash of content before initialization.

Position variants

PositionPlacementWidthTransition
leftLeft edgemin(20rem, 85vw)translateX slide
rightRight edgemin(20rem, 85vw)translateX slide
full-widthCovers full viewport100%Opacity fade

The backdrop and panel use the semantic layer contract: z-modal-backdrop for the backdrop and --st-layer-modal-surface for the panel, ensuring the panel sits deterministically above the backdrop without local arithmetic.

Behavior

Opening and closing

The drawer opens when the bound variable becomes true and closes when it becomes false. Closing can be triggered by:

  • Clicking the backdrop overlay
  • Pressing the Escape key (via a keyboard event listener)
  • Any consumer-provided close button that sets the bound variable to false

Focus trapping and scroll lock

When open, the vanilla JS controller on the panel element:

  • Traps focus inside the panel — Tab and Shift+Tab cycle through focusable elements within the drawer only.
  • Locks scroll on the body — the page behind the drawer cannot be scrolled while the drawer is open.

Focus trapping and scroll lock are implemented in vanilla TypeScript.

Transitions

  • Left/right positions use translateX slide animations with --pt-duration-normal duration and --pt-ease-standard easing.
  • Full-width position uses an opacity fade with the same timing tokens.
  • Backdrop always uses an opacity fade transition.
  • All transitions are implemented via CSS transition classes defined in a scoped <style> block.

Reduced motion

All transitions are disabled when prefers-reduced-motion: reduce is active. The @media query sets transition: none on all transition classes, so the drawer appears and disappears instantly.

Usage

The consumer must wrap the Drawer in an element with data-slot that defines the boolean variable referenced by binding:

<div data-slot="drawer-host">
  <button data-action="open-drawer">Open</button>
  <Drawer
    id="my-drawer"
    binding="drawerOpen"
    label="Navigation menu"
    position="left"
  >
    <!-- drawer content -->
    <button data-action="close-drawer">Close</button>
  </Drawer>
</div>

Note: The binding prop identifies the state variable managed by the vanilla JS controller. The default is "open".

Accessibility

  • role="dialog" and aria-modal="true" on the panel element identify it as a modal dialog to assistive technologies.
  • aria-label (from the label prop) provides an accessible name for the dialog. Always provide a meaningful label.
  • Focus is trapped inside the panel when open via vanilla JS focus trapping, preventing keyboard users from tabbing to elements behind the overlay.
  • Escape key closes the drawer, following the standard modal dismissal pattern.
  • The backdrop is marked aria-hidden="true" so it is not announced by screen readers.

CSS API

BEM root class: drawer

ClassElement
.drawerPanel root
.drawer--leftLeft-positioned panel variant
.drawer--rightRight-positioned panel variant
.drawer--full-widthFull-width panel variant
.drawer-backdropBackdrop overlay

Current consumers

  • MobileMenu — uses binding="mobileOpen" with position="left" for the responsive navigation drawer.
  • Showcase navigation demo page — demonstrates drawer positioning variants.

Vanilla JS implementation

Drawer uses vanilla TypeScript for focus trapping, scroll lock, keyboard handling, and CSS transitions. No framework dependencies are required.

REQ-00050 normative Modal/lightbox components shall implement focus trapping and restoration.
REQ-00053 normative Transient interactive UI shall restore focus upon dismissal.
REQ-00054 normative Modal and overlay components shall define scroll locking behavior while active.
REQ-00085 normative Reduced motion preferences shall be respected.
REQ-00177 implemented The mobile navigation experience shall include a panel/drawer-style interaction model. Relying solely on layout reflow of the main menu is not sufficient.
REQ-00252 implemented A Drawer primitive shall be provided as a reusable slide-in overlay with configurable position (left, right, full-width), focus trapping, scroll locking, ESC and backdrop-click close, and reduced-motion support, serving as the shared foundation for mobile menu, search modal, and other overlay patterns.

Search

Search across pages and articles. Use arrow keys to navigate results.

Search across pages and articles.

Loading search...

Search is unavailable. Please try again later.

    No results for ""

    Try different keywords or fewer words.