---
outline: deep
---

# Drawer

Drawers display supplementary content in a panel that slides in from a screen edge. Commonly used for navigation menus, filters, and detail views without leaving the current page.

**`<l-drawer>`** — Custom Element · Shadow DOM

## Options

### Placement

Set `placement` to the edge the drawer slides from — `start` (default, inline-start), `end` (inline-end), `top` (block-start), or `bottom` (block-end). Open with `command="--show"` on a trigger button; the buttons below open one drawer per edge.

```html
<div class="flex flex-wrap gap-2">
  <button
    type="button"
    class="l-button"
    command="--show"
    commandfor="drawer-start"
  >
    <l-icon name="lucide:panel-left"></l-icon>
    Start
  </button>
  <button
    type="button"
    class="l-button"
    command="--show"
    commandfor="drawer-top"
  >
    <l-icon name="lucide:panel-top"></l-icon>
    Top
  </button>
  <button
    type="button"
    class="l-button"
    command="--show"
    commandfor="drawer-bottom"
  >
    <l-icon name="lucide:panel-bottom"></l-icon>
    Bottom
  </button>
  <button
    type="button"
    class="l-button"
    command="--show"
    commandfor="drawer-end"
  >
    <l-icon name="lucide:panel-right"></l-icon>
    End
  </button>
</div>

<l-drawer
  id="drawer-start"
  title="Drawer"
>
  <button
    slot="close"
    type="button"
    class="l-close"
    data-appearance="ring"
    aria-label="Close"
    command="--hide"
    commandfor="drawer-start"
  ></button>
  <p>This drawer slides in from the start.</p>
</l-drawer>

<l-drawer
  id="drawer-top"
  title="Drawer"
  placement="top"
>
  <button
    slot="close"
    type="button"
    class="l-close"
    data-appearance="ring"
    aria-label="Close"
    command="--hide"
    commandfor="drawer-top"
  ></button>
  <p>This drawer slides in from the top.</p>
</l-drawer>

<l-drawer
  id="drawer-bottom"
  title="Drawer"
  placement="bottom"
>
  <button
    slot="close"
    type="button"
    class="l-close"
    data-appearance="ring"
    aria-label="Close"
    command="--hide"
    commandfor="drawer-bottom"
  ></button>
  <p>This drawer slides in from the bottom.</p>
</l-drawer>

<l-drawer
  id="drawer-end"
  title="Drawer"
  placement="end"
>
  <button
    slot="close"
    type="button"
    class="l-close"
    data-appearance="ring"
    aria-label="Close"
    command="--hide"
    commandfor="drawer-end"
  ></button>
  <p>This drawer slides in from the end.</p>
</l-drawer>
```

```html
<button
  type="button"
  class="l-button"
  command="--show"
  commandfor="drawer-end"
>
  <l-icon name="lucide:panel-right"></l-icon>
  End
</button>

<l-drawer
  id="drawer-end"
  title="Drawer"
  placement="end"
>
  <button
    slot="close"
    type="button"
    class="l-close"
    data-appearance="ring"
    aria-label="Close"
    command="--hide"
    commandfor="drawer-end"
  ></button>
  <p>This drawer slides in from the end.</p>
</l-drawer>
```

### Inset

Add `inset` to float the drawer away from the viewport edges with a uniform gap and rounded corners. Tune the gap with the `--inset-gap` CSS custom property. Works with every `placement` — the buttons below open one drawer per edge.

```html
<div class="flex flex-wrap gap-2">
  <button
    type="button"
    class="l-button"
    command="--show"
    commandfor="drawer-inset-start"
  >
    <l-icon name="lucide:panel-left"></l-icon>
    Start
  </button>
  <button
    type="button"
    class="l-button"
    command="--show"
    commandfor="drawer-inset-top"
  >
    <l-icon name="lucide:panel-top"></l-icon>
    Top
  </button>
  <button
    type="button"
    class="l-button"
    command="--show"
    commandfor="drawer-inset-bottom"
  >
    <l-icon name="lucide:panel-bottom"></l-icon>
    Bottom
  </button>
  <button
    type="button"
    class="l-button"
    command="--show"
    commandfor="drawer-inset-end"
  >
    <l-icon name="lucide:panel-right"></l-icon>
    End
  </button>
</div>

<l-drawer
  id="drawer-inset-start"
  title="Drawer"
  inset
>
  <button
    slot="close"
    type="button"
    class="l-close"
    data-appearance="ring"
    aria-label="Close"
    command="--hide"
    commandfor="drawer-inset-start"
  ></button>
  <p>This drawer floats away from the edges with rounded corners.</p>
</l-drawer>

<l-drawer
  id="drawer-inset-top"
  title="Drawer"
  placement="top"
  inset
>
  <button
    slot="close"
    type="button"
    class="l-close"
    data-appearance="ring"
    aria-label="Close"
    command="--hide"
    commandfor="drawer-inset-top"
  ></button>
  <p>This drawer floats away from the edges with rounded corners.</p>
</l-drawer>

<l-drawer
  id="drawer-inset-bottom"
  title="Drawer"
  placement="bottom"
  inset
>
  <button
    slot="close"
    type="button"
    class="l-close"
    data-appearance="ring"
    aria-label="Close"
    command="--hide"
    commandfor="drawer-inset-bottom"
  ></button>
  <p>This drawer floats away from the edges with rounded corners.</p>
</l-drawer>

<l-drawer
  id="drawer-inset-end"
  title="Drawer"
  placement="end"
  inset
>
  <button
    slot="close"
    type="button"
    class="l-close"
    data-appearance="ring"
    aria-label="Close"
    command="--hide"
    commandfor="drawer-inset-end"
  ></button>
  <p>This drawer floats away from the edges with rounded corners.</p>
</l-drawer>
```

```html
<button
  type="button"
  class="l-button"
  command="--show"
  commandfor="drawer-inset"
>
  <l-icon name="lucide:panel-right"></l-icon>
  End
</button>

<l-drawer
  id="drawer-inset"
  title="Drawer"
  placement="end"
  inset
>
  <button
    slot="close"
    type="button"
    class="l-close"
    data-appearance="ring"
    aria-label="Close"
    command="--hide"
    commandfor="drawer-inset"
  ></button>
  <p>This drawer floats away from the edges with rounded corners.</p>
</l-drawer>
```

### Light dismiss

Add `light-dismiss` to close when the backdrop is clicked.

```html
<button
  type="button"
  class="l-button"
  command="--show"
  commandfor="drawer-light-dismiss"
>
  Open drawer
</button>

<l-drawer
  id="drawer-light-dismiss"
  title="Drawer"
  light-dismiss
>
  <button
    slot="close"
    type="button"
    class="l-close"
    data-appearance="ring"
    aria-label="Close"
    command="--hide"
    commandfor="drawer-light-dismiss"
  ></button>
  <p>Click the backdrop or press Escape to close.</p>
</l-drawer>
```

## Examples

### Navigation

Mobile navigation menu with link items.

```html
<button
  type="button"
  class="l-button"
  command="--show"
  commandfor="drawer-nav"
>
  Menu
</button>

<l-drawer
  id="drawer-nav"
  title="Navigation"
>
  <button
    slot="close"
    type="button"
    class="l-close"
    data-appearance="ring"
    aria-label="Close"
    command="--hide"
    commandfor="drawer-nav"
  ></button>
  <nav>
    <ul class="flex flex-col gap-1">
      <li>
        <a
          href="#"
          class="flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium hover:bg-gray-100"
          >Home</a
        >
      </li>
      <li>
        <a
          href="#"
          class="flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium hover:bg-gray-100"
          >Products</a
        >
      </li>
      <li>
        <a
          href="#"
          class="flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium hover:bg-gray-100"
          >Orders</a
        >
      </li>
      <li>
        <a
          href="#"
          class="flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium hover:bg-gray-100"
          >Customers</a
        >
      </li>
      <li>
        <a
          href="#"
          class="flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium hover:bg-gray-100"
          >Settings</a
        >
      </li>
    </ul>
  </nav>
</l-drawer>
```

### Filters

End-side filter panel with a footer for actions.

```html
<button
  type="button"
  class="l-button"
  command="--show"
  commandfor="drawer-filters"
>
  Filters
</button>

<l-drawer
  id="drawer-filters"
  title="Filters"
  placement="end"
  style="--size: 380px"
>
  <button
    slot="close"
    type="button"
    class="l-close"
    data-appearance="ring"
    aria-label="Close"
    command="--hide"
    commandfor="drawer-filters"
  ></button>
  <div class="flex flex-col gap-6">
    <fieldset>
      <legend class="text-sm font-medium mb-2">Category</legend>
      <div class="flex flex-col gap-2">
        <label class="flex items-center gap-2 text-sm"
          ><input
            type="checkbox"
            checked
          />
          Electronics</label
        >
        <label class="flex items-center gap-2 text-sm"><input type="checkbox" /> Clothing</label>
        <label class="flex items-center gap-2 text-sm"><input type="checkbox" /> Books</label>
      </div>
    </fieldset>
    <fieldset>
      <legend class="text-sm font-medium mb-2">Price range</legend>
      <div class="flex flex-col gap-2">
        <label class="flex items-center gap-2 text-sm"
          ><input
            type="radio"
            name="price"
            checked
          />
          All prices</label
        >
        <label class="flex items-center gap-2 text-sm"
          ><input
            type="radio"
            name="price"
          />
          Under $50</label
        >
        <label class="flex items-center gap-2 text-sm"
          ><input
            type="radio"
            name="price"
          />
          $50 – $100</label
        >
        <label class="flex items-center gap-2 text-sm"
          ><input
            type="radio"
            name="price"
          />
          Over $100</label
        >
      </div>
    </fieldset>
  </div>
  <menu slot="footer">
    <button
      type="button"
      class="l-button"
      command="--hide"
      commandfor="drawer-filters"
    >
      Cancel
    </button>
    <button
      type="button"
      class="l-button"
      data-variant="primary"
    >
      Apply
    </button>
  </menu>
</l-drawer>
```

## Accessibility

### Criteria

- **Role** — Rendered as a native `<dialog>` in the shadow root — built-in `dialog` role and modal semantics
- **Accessible name** — The `title` property is rendered as an `<h2>` inside the drawer header
- **Focus management** — Focus is trapped inside the modal; moves to the first focusable element on open
- **Focus restoration** — Focus returns to the trigger element when the drawer closes
- **Close button** — Consumer provides the close button via `slot="close"` with `aria-label="Close"`
- **Motion** — Slide animation respects `prefers-reduced-motion`

### Rules

- Always set a meaningful `title` — it becomes the drawer heading and accessible name
- Put the close button in `slot="close"` with `class="l-close"` and `command="--hide"` `commandfor="<id>"`
- Use `command="--show"` and `command="--hide"` with `commandfor` pointing at the drawer id. The `--` prefix is mandatory for custom elements

### Keyboard interactions

- `Escape` — Closes the drawer
- `Tab` — Cycles focus through focusable elements inside the drawer
- `Shift + Tab` — Cycles focus backward through focusable elements inside the drawer

## API reference

### Importing

```js
import 'luxen-ui/drawer';
```

### Attributes & Properties

- **placement**: `'start' | 'end' | 'top' | 'bottom' | undefined` — Edge the drawer slides in from. Defaults to the start (inline-start) edge.
- **inset**: `boolean` (default: `false`) — Detach the drawer from the viewport edges, floating it with a uniform gap and rounded corners.
- **title**: `string` — Dialog title rendered in the header and used as the dialog's accessible name.
- **open**: `boolean` (default: `false`) — Whether the dialog is open.
- **light-dismiss**: `boolean` (default: `false`) — Close when the backdrop is clicked.
- **without-header**: `boolean` (default: `false`) — Hide the header entirely (title and close slot).

### Commands

Open and close the drawer by toggling its `open` property, or via the [Invoker Commands API](https://developer.mozilla.org/en-US/docs/Web/API/Invoker_Commands_API) from any light-DOM button. Custom commands must start with `--`.

>
> The Invoker Commands API is [✓ Baseline Newly Available (since 2025-12-12)](https://web-platform-dx.github.io/web-features-explorer/features/invoker-commands/). For older browser versions, load the [`invokers-polyfill`](https://npmx.dev/package/invokers-polyfill) once at app startup:
> 
> ```js
> import 'invokers-polyfill';
> ```
> 

- `--show` — Sets `open = true`.
- `--hide` — Sets `open = false`.

### Events

- **show** (cancelable) — Fired when the drawer is about to open. Cancelable — call `event.preventDefault()` to keep it closed.
- **after-show** — Fired after the open animation completes. Not cancelable.
- **hide** (cancelable) — Fired when the drawer is about to close. Cancelable — call `event.preventDefault()` to keep it open.
- **after-hide** — Fired after the close animation completes. Not cancelable.

### Slots

- **(default)** — Body content.
- **title** — Custom heading element. Overrides the default `<h2>` rendered from the `title` property. Also provides the dialog's accessible name.
- **close** — Close button (typically `<button class="l-close">`).
- **footer** — Footer actions.

### CSS parts

- `dialog` — The native `<dialog>` element.
- `header` — The header wrapper containing the title and close slot.
- `title` — The drawer title heading.
- `body` — The body wrapper around the default slot.
- `footer` — The footer wrapper around the footer slot.

### CSS custom properties

- `--size` (default: `320px`) — Drawer size on the axis perpendicular to its edge (width for `start`/`end`, height for `top`/`bottom`).
- `--border-radius` (default: `6px`) — Drawer border radius on the inner edges (all corners when `inset`).
- `--inset-gap` (default: `1rem`) — Gap between the drawer and the viewport edges when `inset` is set.
- `--shadow` — Drop shadow applied to the floating panel when `inset` is set. Set to `none` to remove it.
- `--show-duration` (default: `200ms`) — Open transition duration.
- `--hide-duration` (default: `200ms`) — Close transition duration.
- `--backdrop` — Backdrop color.
- `--width` (default: `31rem`) — Dialog width.
- `--padding` (default: `1.5rem`) — Padding applied to the header, footer, and inline-padding of the body. Set to `0` to remove all internal spacing (e.g. for edge-to-edge media).
- `--backdrop-blur` (default: `0`) — Backdrop blur amount (any CSS length). `0` means no blur; set e.g. `4px` for a subtle frost.
