Skip to content

Getting started

This guide takes you from installation to a visible, interactive drawer. It uses the package’s primary module entrypoint; for a classic <script> setup, see Browser / CDN.

  • Node.js 20 or newer for installation and builds.
  • A modern browser with ES2020, Pointer Events, and standard dialog-related DOM APIs.
  • Your own panel geometry and theme. The package stylesheet provides motion and interaction behavior, not a finished visual design.
Terminal window
npm install @samline/drawer
<button id="open-filters" type="button">Open filters</button>
import { createDrawer } from '@samline/drawer'
import '@samline/drawer/styles.css'
const trigger = document.querySelector<HTMLElement>('#open-filters')
const drawer = createDrawer({
id: 'filters',
triggerElement: trigger,
direction: 'bottom',
title: 'Filters',
description: 'Refine the result set',
content: 'Add your filter controls here.',
showHandle: true,
closeButton: true
})
// Release the host and listeners when this page or widget unmounts.
window.addEventListener('pagehide', () => drawer.destroy(), { once: true })
[data-drawer-overlay] {
position: fixed;
inset: 0;
z-index: 40;
background: rgb(15 23 42 / 55%);
}
[data-drawer] {
position: fixed;
z-index: 41;
box-sizing: border-box;
width: min(100%, 42rem);
max-height: 85dvh;
padding: 1.25rem;
overflow: auto;
color: #172033;
background: #fff;
outline: none;
}
[data-drawer-direction='bottom'] {
right: 0;
bottom: 0;
left: 0;
margin-inline: auto;
border-radius: 1.25rem 1.25rem 0 0;
}

Click Open filters. The external trigger opens the drawer; the overlay, Escape key, drag gesture, or built-in close button closes it.

createDrawer() returns a controller for the normalized id. Keep it when your integration needs to read, update, subscribe to, or destroy the drawer.

const drawer = createDrawer({ id: 'filters', title: 'Filters' })
drawer.setOpen(true)
drawer.setActiveSnapPoint('420px')
drawer.update({ content: 'Updated body' })
const unsubscribe = drawer.subscribe((snapshot) => {
console.log(snapshot.state.isOpen)
})
unsubscribe()
drawer.destroy()

Important distinctions:

  • setOpen(), setActiveSnapPoint(), and patch() return a state snapshot.
  • patch() accepts only CommonDrawerOptions; it cannot update title, content, triggers, or classes.
  • update() accepts all VanillaDrawerOptions and returns another controller wrapper for the same id.
  • Subscribers run immediately and on every controller publication, including some same-value writes.
  • Treat drawer.options and snapshot objects as read-only. They expose live nested references; mutate state through controller methods so rendering and subscribers stay synchronized.

See Controller and registry for every property, method, return value, and the equivalent id-based helpers.

title, description, and content accept text, numbers, existing elements, or lazy element factories.

const form = document.createElement('form')
form.innerHTML = '<label>Search <input name="q" /></label><button>Apply</button>'
createDrawer({
id: 'search',
title: 'Search',
content: form // moved into the drawer, not cloned
})
createDrawer({
id: 'clock',
title: 'Current time',
content: () => {
const time = document.createElement('time')
time.textContent = new Date().toLocaleTimeString()
return time
}
})

Lazy factories run again whenever the dialog subtree is rebuilt, including after close and reopen. Existing elements become detached when that subtree is removed; retain your own reference if you need to reattach or reuse one. Read the exact VanillaRenderable contract.

  1. createDrawer() registers one host per id. A closed drawer keeps only that host and its optional built-in trigger.
  2. Opening mounts the dialog and, when overlay resolves to true, a backdrop. overlay defaults to modal. Creating with open: true skips the entrance animation; create closed and then call setOpen(true) to animate.
  3. Closing marks visual nodes closed, releases focus and page effects, then removes those nodes after the exit timeout. It keeps the id and host registered.
  4. Destroying removes the registry entry, host, listeners, timers, and effects owned by that drawer. It does not call onClose().

Reusing an id shallow-merges options into the existing drawer. Use a unique id for an independent drawer and call destroy() when its integration unmounts.

  • Lifecycle and state explains identity, close versus destroy, callback order, subscriptions, and SPA cleanup.
  • Gestures and snap points explains drag permission, snap ordering, nesting, and scaled backgrounds.
  • Accessibility and focus covers naming, modal focus ownership, dismissal, and interactive content.
  • Configuration documents every option, default, accepted value, and important edge case.
  • Controller and registry helps choose between instance methods, id-based helpers, and the headless controller.
  • Entrypoints compares ESM, CommonJS, the browser namespace, and the standalone IIFE.
  • Styling defines the DOM, ARIA, selectors, inline styles, and all four directions.
  • Recipes covers snap points, nesting, forms, focus, triggers, multiple drawers, and cleanup.
  • TypeScript lists all exported types, constants, and return shapes.