<!-- https://svgrid.com/docs/help/ui-components/sv-drawer/ - SvGrid documentation as markdown. Index of every page: https://svgrid.com/llms.txt -->

# SvDrawer

An edge-anchored side sheet - right, left, top, or bottom - with a backdrop and
the full dialog accessibility contract.

`SvDrawer` slides a panel in from any edge and is built entirely on the shared
primitives (`createFocusTrap`, `lockScroll`, `createDismissableLayer`), so its
focus, scroll-lock, and Escape/backdrop behaviour is identical to
[SvModal](https://svgrid.com/docs/help/ui-components/sv-modal/) and nested overlays close top-first. Set `sheet` for a
mobile bottom-sheet with a grab handle and swipe-down-to-close. Controlled via a
bindable `open`.

Related: [SvModal](https://svgrid.com/docs/help/ui-components/sv-modal/) · [SvToaster](https://svgrid.com/docs/help/ui-components/sv-toaster/) · [Overlays & menus overview](https://svgrid.com/docs/help/ui-components/overlays/)

## Installation

Add it with the CLI - this drops a ready-to-edit `SvDrawer` starter into your app:

<div data-docs-add="add drawer"></div>

Prefer to see it first? `npx @svgrid/ui try drawer` opens it in a throwaway sandbox - no project needed.

Or install the package and import it directly. `SvDrawer` ships free in
`@svgrid/grid` (dependency-free):

<div data-docs-install="@svgrid/grid"></div>

The examples on this page import from `@svgrid/grid`:

```svelte
<script lang="ts">
  import { SvButton, SvDrawer } from '@svgrid/grid'
</script>
```

```ts
import { SvDrawer } from '@svgrid/grid'
```

## Example

> Live demo: App overlays: drawer, context menu, toasts - https://svgrid.com/demos/331-app-overlays/

```svelte
<script lang="ts">
  import { SvDrawer, SvButton } from '@svgrid/grid'
  let open = $state(false)
</script>

<SvButton onclick={() => (open = true)}>Filters</SvButton>

<SvDrawer bind:open side="right" title="Filters">
  <p>Body content.</p>
  {#snippet footer()}<SvButton variant="primary" onclick={() => (open = false)}>Apply</SvButton>{/snippet}
</SvDrawer>
```

## Props

| Prop              | Type                                   | Default             | Description                                                  |
| ----------------- | -------------------------------------- | ------------------- | ---------------------------------------------------------- |
| `open`            | `boolean`                              | `false`             | Controlled, bindable open state.                           |
| `onClose`         | `() => void`                           | -                   | Called whenever the drawer closes.                         |
| `onClosed`        | `() => void`                           | -                   | Fired after the exit animation ends and the drawer leaves the DOM. |
| `side`            | `right` \| `left` \| `top` \| `bottom` | `right`             | Edge the drawer slides from.                               |
| `size`            | `string`                               | per-axis default    | Panel size along its axis; any CSS length.                 |
| `title`           | `string`                               | -                   | Header heading; also names the drawer for a11y.            |
| `closeOnBackdrop` | `boolean`                              | `true`              | Close when the backdrop is clicked.                        |
| `closeOnEsc`      | `boolean`                              | `true`              | Close on the Escape key.                                   |
| `hideClose`       | `boolean`                              | `false`             | Hide the header close (x) button.                          |
| `sheet`           | `boolean`                              | `false`             | Render as a mobile bottom-sheet (overrides `side`).        |
| `ariaLabel`       | `string`                               | -                   | Accessible name when there is no visible `title`.          |
| `children`        | `Snippet`                              | -                   | Drawer body.                                               |
| `footer`          | `Snippet`                              | -                   | Footer content, typically action buttons.                  |

## Examples

### Record editor

The classic "click a row, slide in the record" flow - anchor right, give it a
title, and put the save action in the footer:

```svelte
<SvDrawer bind:open side="right" title="Edit contact" size="min(480px, 92vw)">
  <!-- form fields -->
  {#snippet footer()}
    <SvButton onclick={() => (open = false)}>Cancel</SvButton>
    <SvButton variant="primary" onclick={save}>Save</SvButton>
  {/snippet}
</SvDrawer>
```

### Mobile bottom-sheet

Set `sheet` for a bottom-anchored sheet with a grab handle; users can swipe it
down past the threshold to dismiss:

```svelte
<SvDrawer bind:open sheet title="Sort by">
  <!-- options -->
</SvDrawer>
```

### Row detail from a grid

Pair a drawer with a grid's row-select to build a master-detail view: keep the
list visible on the left while the selected record slides in from the right.
Track the row you opened and clear it on close:

```svelte
<script lang="ts">
  import { SvDrawer, SvButton } from '@svgrid/grid'
  let selected = $state(null)
  const open = $derived(selected != null)

  function openRow(row) { selected = row }
</script>

<SvDrawer open={open} onClose={() => (selected = null)}
  side="right" title={selected?.name} size="min(480px, 92vw)">
  {#if selected}
    <dl>
      <dt>Email</dt><dd>{selected.email}</dd>
      <dt>Status</dt><dd>{selected.status}</dd>
    </dl>
  {/if}
  {#snippet footer()}
    <SvButton variant="primary" onclick={() => (selected = null)}>Done</SvButton>
  {/snippet}
</SvDrawer>
```

Tip: a drawer shares SvModal's dialog contract, so focus is trapped and page
scroll is locked while it is open. For a persistent navigation rail that should
not trap focus or dim the page, reach for a plain sidebar instead - a drawer is
for a modal, dismissable surface.

### Any edge

Choose `side="left"` for navigation, or `top` / `bottom` for banners and command
surfaces; `size` accepts any CSS length along the panel's axis.

## Accessibility

- The panel is `role="dialog"` with `aria-modal="true"`; `title` provides
  `aria-labelledby`, or supply `ariaLabel` when there is no visible title.
- Focus is trapped and page scroll locked while open, released on close, exactly
  as in [SvModal](https://svgrid.com/docs/help/ui-components/sv-modal/).
- Escape and backdrop dismissal follow `closeOnEsc` / `closeOnBackdrop` through
  the shared layer stack.

## Which edge, and how much of it

`side` picks the edge and `size` the extent. A right drawer reads as a detail
panel; a bottom one reads as a sheet on a phone, which is what `sheet` styles it
as.

```svelte
<script lang="ts">
  import { SvDrawer, SvButton } from '@svgrid/grid'

  let right = $state(false)
  let bottom = $state(false)
</script>

<SvButton onclick={() => (right = true)}>Open detail panel</SvButton>
<SvButton variant="outline" onclick={() => (bottom = true)}>Open sheet</SvButton>

<SvDrawer open={right} onClose={() => (right = false)} side="right" size="360px" title="Row detail">
  <p>Everything about the selected row.</p>
</SvDrawer>

<SvDrawer open={bottom} onClose={() => (bottom = false)} side="bottom" size="45%" sheet title="Actions">
  <p>The phone-shaped version of the same idea.</p>
</SvDrawer>
```

## See also

- [Overlays overview](https://svgrid.com/docs/help/ui-components/overlays/) - the whole floating-surface family.
- [SvModal](https://svgrid.com/docs/help/ui-components/sv-modal/) - the same contract as a centered dialog.
- [SvToaster](https://svgrid.com/docs/help/ui-components/sv-toaster/) - transient notifications for post-action feedback.

---

SvGrid is the Svelte 5 data grid (`npm install @svgrid/grid`, MIT). This page: https://svgrid.com/docs/help/ui-components/sv-drawer/ . All docs: https://svgrid.com/llms.txt
