JavaScript Drawer
@lemonadejs/drawer · ✓ 17 contract checks · framework-agnostic · zero dependencies
<Drawer /> — a side panel sliding from an edge, built ON
the Modal primitive: position left/right are already full-viewport-height
side panels and bottom is the sheet mode, so the drawer is a thin
composition — anchor mapping and the slide-in animation (CSS keyframes
scoped by data-anchor on the wrapper). The header IS Modal’s own
(title + close button): one header implementation across the catalog.
Contract:
bind two-way open state (named visible internally — assignment
through the bound chain is SILENT, no onopen/onclose echo)
anchor ” = left | ‘right’ | ‘bottom’ — live while open (Modal’s
position prop is reactive)
width panel width in px (left/right; bottom is full width via CSS)
backdrop dimmed overlay behind the panel
closable backdrop click + Escape close the drawer
title optional header row with a close ×
label accessible name for the title-less drawer (the panel is a
role=dialog — it must never be unnamed)
onclose(origin): ‘button’ | ‘backdrop’ | ‘escape’ | ‘api’.
Example
import { html } from 'lemonadejs';
import Drawer from '@lemonadejs/drawer';
const FOLDERS = ['Inbox', 'Starred', 'Sent', 'Drafts', 'Trash'];
const App = (props, { state }) => {
const open = state(false);
const folder = state('Inbox');
const go = (name) => {
folder.value = name;
open.value = false;
};
return html`<div>
<button onclick="${() => (open.value = true)}">☰ Folders</button>
<p>Showing: <b>${folder}</b></p>
<${Drawer} bind="${open}" title="Mailbox" width="260">
${FOLDERS.map((name) => html`<button onclick="${() => go(name)}"
style="display:block;width:100%;text-align:left;padding:10px 16px;border:0;background:none;cursor:pointer">
${name}
</button>`)}
</${Drawer}>
</div>`;
};Installation
npm install @lemonadejs/drawer
import Drawer from '@lemonadejs/drawer';
import '@lemonadejs/drawer/style.css';
Three deployment forms, one component:
html`<${Drawer} />` // by value (no registration)
setComponents({ Drawer }); // then <Drawer /> by name anywhere
createWebComponent(Drawer); // <lm-drawer> in plain HTML/any framework
Props
Every declared prop arrives as a live state — pass a value for a snapshot or a state for a two-way live wire. Attribute strings are coerced to the declared type.
| Prop | Type | Default | Description |
|---|---|---|---|
bind | boolean | — | Two-way bound value. .set() fires onchange; plain assignment is silent. two-way open state |
anchor | string | '' | ” = left |
width | number | 280 | panel width px (left/right) |
backdrop | boolean | true | dimmed overlay |
closable | boolean | true | backdrop click + Escape close |
title | string | '' | optional header row with a close × |
label | string | "Drawer" | accessible name when there is no visible title (aria-label) |
Events
All event names are lowercase (the platform convention — LJS-305 warns otherwise).
onopenonclose— (origin)
API
import { ref } from 'lemonadejs';
const drawer = ref();
html`<${Drawer} ref="${drawer}" />`;
// drawer.current.open(...) · drawer.current.close(...) · drawer.current.toggle(...)
open()close()toggle()
Styling
All classes follow the lm-drawer-* convention; visual variants are data-*
attributes on the root. Override freely — there is no styling engine to fight.
Contract
The machine-readable schema ships with the package:
import contract from '@lemonadejs/drawer/contract.json';
verify.json carries the conformance proof produced by verify(Drawer).
Looking for the v5 plugin? See the archived v5 documentation.