Skip to content

HyDrawer

A drawer component that slides in from the edge of the screen.

Key Features

  • Slides in from any edge (start, end, top, bottom)
  • Uses logical properties for RTL support
  • Three variants: temporary (modal), persistent, permanent
  • Mini/collapsed mode with icon-only display
  • Configurable size presets or custom values
  • Focus trapping and management (temporary variant)
  • Cancelable close events

Variants

  • Temporary (default): Overlay drawer, modal by default (scrim + focus trap); set modal={false} for a non-modal floating panel (Popover API, no scrim, page stays interactive)
  • Persistent: Toggleable drawer without overlay, content shifts aside
  • Permanent: Always visible, integrated into layout
  • Responsive: One declaration, one content source — morphs between a docked inline rail (region ≥ docked-at) and an overlay sheet with a self-trigger (region < docked-at). A ResizeObserver on the parent region drives the switch and reflects data-mode="docked|overlay" so the surrounding layout can key on the active mode at the same breakpoint.

Mini Mode

When mini is enabled, the drawer collapses to show only icons. Use expand-on-hover to expand the mini drawer when hovered.

Native Dialog Approach

The temporary variant leverages the browser's native <dialog> element which provides:

  • Top Layer Rendering: Drawer appears above all other content automatically
  • Inert Background: Modal variant makes outside content non-interactive
  • Backdrop Support: Native ::backdrop pseudo-element for overlay styling
  • Escape Key Handling: Built-in keyboard dismissal with the cancel event
  • Accessibility: Implicit role="dialog" and aria-modal="true"

Initial Focus

On show, the drawer follows the same [autofocus] discovery contract as hy-dialog: it queries for a slotted descendant marked with autofocus, emits a cancelable initial-focus event, and (if not prevented) focuses the resolved target. When no marker is set, focus falls back to the close button (or collapse trigger) so keyboard users land somewhere sensible. See .claude/rules/focus.md for the contract.

Accessibility Features

  • Native dialog accessibility (role="dialog", aria-modal based on variant)
  • Configurable aria-labelledby and aria-label
  • Configurable close button label for internationalization (close-label)
  • Configurable collapse trigger label for internationalization (collapse-label)
  • Focus restoration to trigger element on close
  • Autofocus discovery via [autofocus] marker; cancelable via initial-focus event
  • Escape key dismissal with cancelable event
  • Backdrop click dismissal
  • Body scroll lock when open (temporary variant)
  • Focus management (focus not obscured)
  • Screen reader announcements on open/close

Examples

Basic Usage (Temporary/Modal)

html
<hy-drawer label="Settings" open>
  <p>Configure your preferences here.</p>
</hy-drawer>

Self-managing trigger (no open wiring)

html
<hy-drawer label="Filters" placement="end">
  <hy-button slot="trigger">Filters</hy-button>
  <p>Filter facets go here.</p>
</hy-drawer>

Responsive morph (docked rail ↔ overlay sheet)

html
<!-- Wide region: docked inline rail. Narrow region: a "Filters" trigger
opens an overlay sheet. Same fragment, same single content source. -->
<hy-drawer variant="responsive" docked-at="md" placement="start" label="Filters">
  <hy-button slot="trigger">Filters</hy-button>
  <p>Filter facets — authored once.</p>
</hy-drawer>

Persistent Drawer (Sidebar)

html
<hy-drawer variant="persistent" placement="start" open>
  <nav>
    <a href="/home">Home</a>
    <a href="/settings">Settings</a>
  </nav>
</hy-drawer>

Permanent Drawer (Always Visible)

html
<hy-drawer variant="permanent" placement="start">
  <nav>Navigation always visible</nav>
</hy-drawer>

Mini Drawer with Expand on Hover

html
<hy-drawer variant="permanent" placement="start" mini expand-on-hover>
  <nav>
    <hy-icon name="home"></hy-icon> <span>Home</span> <hy-icon name="settings"></hy-icon>
    <span>Settings</span>
  </nav>
</hy-drawer>

Collapsible Sidebar with Collapse Trigger

html
<hy-drawer variant="permanent" placement="start" collapse-trigger>
  <nav>
    <hy-icon name="home"></hy-icon> <span>Home</span> <hy-icon name="settings"></hy-icon>
    <span>Settings</span>
  </nav>
</hy-drawer>

With Placement

html
<hy-drawer label="Navigation" placement="start" open>
  <nav>Navigation items here</nav>
</hy-drawer>
html
<hy-drawer label="Edit Profile">
  <form>Form content here</form>
  <div slot="footer">
    <hy-button variant="neutral">Cancel</hy-button>
    <hy-button variant="brand">Save</hy-button>
  </div>
</hy-drawer>

Programmatic Control

javascript
<hy-button id="openBtn">Open Drawer</hy-button>
<hy-drawer id="myDrawer" label="Settings">
<p>Drawer content</p>
</hy-drawer>

<script>
const drawer = document.getElementById('myDrawer');
const openBtn = document.getElementById('openBtn');

openBtn.addEventListener('click', () => drawer.show());

// Listen for close requests (cancelable)
drawer.addEventListener('request-close', (e) => {
if (hasUnsavedChanges) {
e.preventDefault(); // Keep drawer open
showUnsavedWarning();
}
});
</script>

Custom Size

html
<hy-drawer label="Wide Drawer" size="600px" placement="end">
  <p>This drawer has a custom width.</p>
</hy-drawer>

Events

  • show - Fired when the drawer opens. Detail: { source: HyVisibilitySource }
  • hide - Fired when the drawer closes (after animation). Detail: { source: HyVisibilitySource }
  • request-close - Fired before closing; cancelable to prevent close. Detail: { source: HyVisibilitySource }
  • initial-focus - Fired on show after the autofocus discovery runs. Cancelable: call event.preventDefault() to skip the default focus and apply your own. Detail: HyInitialFocusDetail

Slots

  • trigger - An element that toggles the drawer on click (self-managing, no open wiring required). ARIA (aria-haspopup, aria-controls, aria-expanded) is applied automatically and focus returns to the trigger on close. A controlled open still wins when both are present. Most useful on the temporary variant.
  • default - The main content displayed in the drawer body
  • header - Custom header content that replaces the default title.
  • header-actions - Additional actions in the header (alongside close button)
  • footer - Footer content, typically action buttons

Methods

willUpdate()

Generates unique IDs before the first render to prevent update-after-update warnings.

firstUpdated()

Resolve a for= trigger and apply initial trigger ARIA after the first render (the <dialog> id exists by now).

updated()

Single source of truth: all side effects for open/close happen here.

disconnectedCallback()

Clean up when element is removed from the DOM.

show()

Opens the drawer programmatically. Has no effect on permanent variant (always visible).

hide()

Closes the drawer programmatically. Emits a cancelable request-close event first. Has no effect on permanent variant (cannot be closed).

Parameters:

  • source - How the close was triggered

Built with Lit. Documented with VitePress.