---
title: "JavaScript Drawer"
description: "The LemonadeJS Drawer block for JavaScript: Anchored side panels and the bottom sheet. Contract-verified, framework-agnostic, zero dependencies, with a live example."
source: https://lemonadejs.com/docs/plugins/drawer/
---

<link rel="stylesheet" href="/v6/modal.css">
<link rel="stylesheet" href="/v6/drawer.css">

# 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

<!--example-->

```js
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

```bash
npm install @lemonadejs/drawer
```

```js
import Drawer from '@lemonadejs/drawer';
import '@lemonadejs/drawer/style.css';
```

Three deployment forms, one component:

```js
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 | right | bottom |
| `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).

- `onopen`
- `onclose` — (origin)

## API

```js
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:

```js
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](/docs/v5/plugins/).