Component
Drawer
<DomDrawer>A slide-out panel for navigation menus, filters, or secondary app panels with backdrop dismiss, drag dismiss, scroll lock, and focus trap built in.
Playground
Try every prop live
Drawer playground
Edit any prop on the right — toggle static to disable backdrop dismiss. Source shows the SFC you would write; Data shows the live props object bound to the component.
<script setup>
import { reactive } from 'vue';
import { DomDrawer } from '@getdom/studio/vue';
// The playground state is just a reactive props object. The <Playground>
// wrapper introspects this via defineExpose to drive its inspector form, but
// nothing here is playground-specific — copy this file and you have a
// drop-in drawer with its props stored in one place.
const data = reactive({
side: 'right',
title: 'Drawer',
width: 'var(--container-sm)',
static: false,
modelValue: false,
});
defineExpose({ data });
</script>
<template>
<DomDrawer
v-bind="data"
@update:modelValue="data.modelValue = $event"
>
<template #trigger>
<button
slot="trigger"
type="button"
class="inline-flex h-10 items-center rounded-full bg-primary px-4 text-sm font-medium text-primary-fg hover:opacity-90"
>Open drawer</button>
</template>
<nav class="space-y-1 p-4">
<a class="block rounded-md px-3 py-2 hover:bg-secondary" href="#">Home</a>
<a class="block rounded-md px-3 py-2 hover:bg-secondary" href="#">Inbox</a>
<a class="block rounded-md px-3 py-2 hover:bg-secondary" href="#">Settings</a>
</nav>
</DomDrawer>
</template>
Demo
Right edge (default)
A navigation menu sliding in from the right — the same interaction model as a mobile nav drawer.
<script setup>
import { ref } from 'vue';
import { DomDrawer, DomButton } from '@getdom/studio/vue';
const open = ref(false);
</script>
<template>
<DomButton @click="open = true">Open menu</DomButton>
<DomDrawer v-model="open" title="Navigation">
<nav class="flex flex-col">
<a
href="#"
class="border-b border-border px-4 py-4 text-sm font-medium text-fg hover:bg-secondary/60"
@click.prevent="open = false"
>Home</a>
<a
href="#"
class="border-b border-border px-4 py-4 text-sm font-medium text-muted-fg hover:bg-secondary/60 hover:text-fg"
@click.prevent="open = false"
>Projects</a>
<a
href="#"
class="border-b border-border px-4 py-4 text-sm font-medium text-muted-fg hover:bg-secondary/60 hover:text-fg"
@click.prevent="open = false"
>Settings</a>
</nav>
</DomDrawer>
</template>
Demo
Left edge
Set side="left" for filter panels, detail views, or any secondary panel anchored to the opposite edge.
<script setup>
import { ref } from 'vue';
import { DomDrawer, DomButton } from '@getdom/studio/vue';
const open = ref(false);
</script>
<template>
<DomButton variant="secondary" @click="open = true">Open filters</DomButton>
<DomDrawer v-model="open" side="left" title="Filters" width="var(--container-xs)">
<div class="space-y-4 p-4 text-sm text-muted-fg">
<p>Sort by date, status, or assignee.</p>
<DomButton variant="secondary" data-close class="w-full">Apply</DomButton>
</div>
</DomDrawer>
</template>
Demo
Bottom edge
Set side="bottom" for sheet-style drawers. The small pill handle can be dragged down to close on desktop or mobile.
<script setup>
import { ref } from 'vue';
import { DomButton, DomDrawer } from '@getdom/studio/vue';
const open = ref(false);
</script>
<template>
<DomButton variant="secondary" @click="open = true">Open bottom drawer</DomButton>
<DomDrawer
v-model="open"
side="bottom"
title="Quick actions"
width="100%"
>
<div class="grid gap-3 p-4 sm:grid-cols-3">
<DomButton variant="secondary" class="justify-center">Duplicate</DomButton>
<DomButton variant="secondary" class="justify-center">Share</DomButton>
<DomButton class="justify-center" data-close>Done</DomButton>
</div>
</DomDrawer>
</template>
Demo
Programmatic control
Open, close, or toggle via a template ref — or drive state with v-model from script.
<script setup>
import { ref } from 'vue';
import { DomDrawer, DomButton } from '@getdom/studio/vue';
const drawerRef = ref(null);
</script>
<template>
<div class="flex flex-wrap items-center justify-center gap-2">
<DomButton @click="drawerRef?.open()">Open</DomButton>
<DomButton variant="secondary" @click="drawerRef?.close()">Close</DomButton>
<DomButton variant="ghost" @click="drawerRef?.toggle()">Toggle</DomButton>
</div>
<DomDrawer ref="drawerRef" title="Via template ref">
<p class="p-4 text-sm text-muted-fg">
Call <code class="text-fg">open()</code>, <code class="text-fg">close()</code>, or
<code class="text-fg">toggle()</code> on the component ref — or bind <code class="text-fg">v-model</code> from script.
</p>
</DomDrawer>
</template>
Reference
Props
Control props
| Name | Type | TS | Default | Description |
|---|---|---|---|---|
modelValue | boolean | boolean | false | Whether the drawer is open. |
side | 'left' | 'right' | 'bottom' | string | 'right' | Edge the panel slides in from. |
title | string | string | '' | Optional heading at the top of the panel. |
width | [object Object] | [object Object] | [object Object] | [object Object] | [object Object] | [object Object] | [object Object] | [object Object] | [object Object] | [object Object] | [object Object] | [object Object] | [object Object] | string | 'var(--container-sm)' | Panel width as a CSS length — pick a Tailwind container variable or type your own (e.g. 50rem). |
static | boolean | boolean | false | Disable backdrop-click dismiss. Esc and data-close still work. |
Auto-generated from Drawer.props and inline _edit hints.
Slots
| Name | Scope | Description |
|---|---|---|
| #trigger | — | Element that opens the drawer when clicked (omit when controlling open state yourself). |
| #(default) | — | Panel body. |
| #header | — | Replace the default header row (title + close button). |
| #footer | — | Optional footer below the scrollable body. |
Keyboard
- EscCloses the drawer.
- Tab / Shift+TabFocus stays within the panel while open.
- Click backdropDismisses the drawer. Add `static` on the host to disable.
- Drag handleBottom drawers can be dragged down from the pill handle to close.