Skip to content
View as Markdown

Disclosures

Drawer

An edge-anchored panel that slides in over the page, built on the native <dialog> element and the browser's top layer.

Import

TSX
import { Drawer } from "@loamui/core";

Usage

Every example has a CSS tab. That’s the real, complete stylesheet for the component: plain, static CSS, with nothing running in the browser.

Basic usage

Compose the drawer from parts. The Popup is a native <dialog> opened with showModal(): top layer, backdrop, focus containment, Escape and focus restore all come from the browser. A Drawer is a Modal pinned to an edge with a slide transition.

Navigation

Jump to a section of the app.

Sides

side anchors the panel to an edge and picks the slide direction. It is logical (start / end / top / bottom), so start and end follow the writing mode: inline-start is the left in LTR, the right in RTL.

Side: start

start/end set the panel width; top/bottom set its height. Both follow writing mode.

Side: end

start/end set the panel width; top/bottom set its height. Both follow writing mode.

Side: top

start/end set the panel width; top/bottom set its height. Both follow writing mode.

Side: bottom

start/end set the panel width; top/bottom set its height. Both follow writing mode.

Header with a close button

A header row with an × is a composition pattern, not configuration: compose Drawer.Title and Drawer.Close however your design needs.

Filters

Refine the results shown in the list.

Guidance

When to use it

  • For navigation and secondary content that shouldn't take over the whole screen: a mobile menu, a filter panel, a details side-sheet.
  • When the surrounding page context should stay visible behind a dimmed backdrop, and the panel is casually dismissible.
  • On narrow screens, to hold navigation that sits in a persistent sidebar on wider ones.

When not to

  • For a focused, must-answer task or a destructive confirmation, use Modal to centre the dialog on the decision.
  • For a small set of actions opened from a control, use Menu; for supplementary content anchored to a trigger, use Popover.
  • For primary navigation that fits on wide screens, use a persistent sidebar or nav bar rather than hiding it behind a trigger.

Accessibility

  • Built on the native <dialog> opened with showModal(): the browser provides the top layer, ::backdrop, real focus containment, Escape handling, and restores focus to the trigger on close, none of it re-implemented in JavaScript.
  • Drawer.Title and Drawer.Description automatically label and describe the dialog via aria-labelledby / aria-describedby.
  • Drawer.Trigger carries aria-haspopup="dialog" and aria-expanded, which follows the open state, so a screen reader says what the button opens and whether it is open.
  • Light dismiss (clicking the backdrop) uses the closedby attribute where supported, with a small feature-detected coordinate-check fallback elsewhere: no polyfills, per the browser support policy.
  • The slide transition lives inside prefers-reduced-motion: no-preference, so users who ask for reduced motion get an instant open with no movement.
  • A forced-colors border keeps the panel edge visible when background colours are overridden; body scroll is locked while open.

How it works

Anchor to a logical edge

side is logical, not physical: start and end follow the writing mode so a start drawer opens from the left in LTR and the right in RTL, matching where users expect the back edge to be. Reach for top / bottom for sheets: a bottom sheet is the thumb-friendly choice for actions on touch devices.

A drawer is dismissible by nature

Unlike an alert dialog, a drawer light-dismisses: clicking the backdrop or pressing Escape closes it. That is the right contract for navigation and browsing. Never put a decision the user must not dismiss by accident in a drawer; use Modal's alert for that.

Always render a Title

Drawer.Title labels the dialog via aria-labelledby; it is what screen readers announce on open. A drawer used purely for navigation should still carry a Title (for example "Navigation") so its purpose is announced, even if you hide it visually; a Popup with neither a Title nor an aria-label is reported in development.

Keep the trigger where the focus returns

The browser restores focus to the trigger when the drawer closes. Keep a single, stable trigger (a hamburger button) rather than swapping it out while open, so keyboard and screen-reader users land back where they started.

Parts

Drawer.Root

Groups the parts and owns open state (controlled or uncontrolled). Renders no element of its own.

PropTypeDefaultDescription
openboolean—Controlled open state.
defaultOpenbooleanfalseUncontrolled initial state.
onOpenChange(open: boolean) => void—Fires when the drawer opens or closes.

Drawer.Trigger

A LoamUI Button that opens the drawer.

PropTypeDefaultDescription
renderelement | (props) => node—Substitute your own action element (an icon button); the wiring merges on.
...othersButtonHTMLAttributes—Forwarded to the button.

Drawer.Popup

The native <dialog>. side anchors it to an edge and sets the slide direction; the short-axis extent comes from --loam-drawer-size.

PropTypeDefaultDescription
side"start" | "end" | "top" | "bottom""start"Edge to anchor to.

Drawer.Title / Drawer.Description

Label and describe the dialog for assistive technology.

Drawer.Close

A LoamUI Button that closes the drawer; compose as many as you need (a footer action, a header ×). Native <button> props are forwarded.

PropTypeDefaultDescription
renderelement | (props) => node—Substitute your own element; it receives the close wiring.

Custom properties

PropertySyntaxDefaultDescription
--loam-drawer-sizeCSS length24remThe panel's short-axis extent: width for start/end, height for top/bottom. The panel never exceeds the viewport either way.