Components
A drawer that smoothly slides in & out of the screen.

Usage

Use a Button or any other component in the default slot of the Drawer.

Then, use the #content slot to add the content displayed when the Drawer is open.

<template>
  <UDrawer>
    <UButton
      label="Open"
      color="gray"
      variant="subtle"
      trailing-icon="i-heroicons-chevron-up-20-solid"
    />

    <template #content>
      <Placeholder class="h-48 m-4" />
    </template>
  </UDrawer>
</template>

You can also use the #header, #body and #footer slots to customize the Drawer's content.

Title

Use the title prop to set the title of the Drawer's header.

<template>
  <UDrawer title="Drawer with title">
    <UButton
      label="Open"
      color="gray"
      variant="subtle"
      trailing-icon="i-heroicons-chevron-up-20-solid"
    />

    <template #body>
      <Placeholder class="h-48" />
    </template>
  </UDrawer>
</template>

Description

Use the description prop to set the description of the Drawer's header.

<template>
  <UDrawer
    title="Drawer with description"
    description="Lorem ipsum dolor sit amet, consectetur adipiscing elit."
  >
    <UButton
      label="Open"
      color="gray"
      variant="subtle"
      trailing-icon="i-heroicons-chevron-up-20-solid"
    />

    <template #body>
      <Placeholder class="h-48" />
    </template>
  </UDrawer>
</template>

Direction

Use the direction prop to control the direction of the Drawer. Defaults to bottom.

<template>
  <UDrawer direction="top">
    <UButton
      label="Open"
      color="gray"
      variant="subtle"
      trailing-icon="i-heroicons-chevron-up-20-solid"
    />

    <template #content>
      <Placeholder class="h-96 m-4" />
    </template>
  </UDrawer>
</template>
<template>
  <UDrawer direction="right">
    <UButton
      label="Open"
      color="gray"
      variant="subtle"
      trailing-icon="i-heroicons-chevron-up-20-solid"
    />

    <template #content>
      <Placeholder class="w-96 m-4" />
    </template>
  </UDrawer>
</template>

Handle

Use the handle prop to control whether the Drawer has a handle or not. Defaults to true.

<template>
  <UDrawer :handle="false">
    <UButton
      label="Open"
      color="gray"
      variant="subtle"
      trailing-icon="i-heroicons-chevron-up-20-solid"
    />

    <template #content>
      <Placeholder class="h-48 m-4" />
    </template>
  </UDrawer>
</template>

Overlay

Use the overlay prop to control whether the Drawer has an overlay or not. Defaults to true.

<template>
  <UDrawer :overlay="false">
    <UButton
      label="Open"
      color="gray"
      variant="subtle"
      trailing-icon="i-heroicons-chevron-up-20-solid"
    />

    <template #content>
      <Placeholder class="h-48 m-4" />
    </template>
  </UDrawer>
</template>

Scale background

Use the should-scale-background prop to scale the background when the Drawer is open, creating a visual depth effect.

<template>
  <UDrawer should-scale-background>
    <UButton
      label="Open"
      color="gray"
      variant="subtle"
      trailing-icon="i-heroicons-chevron-up-20-solid"
    />

    <template #content>
      <Placeholder class="h-48 m-4" />
    </template>
  </UDrawer>
</template>
Make sure to add the vaul-drawer-wrapper directive to a parent element of your app to make this work.
app.vue
<template>
  <UApp>
    <div class="bg-white dark:bg-gray-900" vaul-drawer-wrapper>
      <NuxtLayout>
        <NuxtPage />
      </NuxtLayout>
    </div>
  </UApp>
</template>
nuxt.config.ts
export default defineNuxtConfig({
  app: {
    rootAttrs: {
      'vaul-drawer-wrapper': '',
      'class': 'bg-white dark:bg-gray-900'
    }
  }
})

Examples

Control open state

You can control the open state by using the default-open prop or the v-model:open directive.

<script setup lang="ts">
const open = ref(false)

defineShortcuts({
  o: () => (open.value = !open.value)
})
</script>

<template>
  <UDrawer v-model:open="open">
    <UButton
      label="Open"
      color="gray"
      variant="subtle"
      trailing-icon="i-heroicons-chevron-up-20-solid"
    />

    <template #content>
      <Placeholder class="h-48 m-4" />
    </template>
  </UDrawer>
</template>
In this example, press O to toggle the Drawer.
This allows you to move the trigger outside of the Drawer or remove it entirely.

Use the #footer slot to add content after the Drawer's body.

<script setup lang="ts">
const open = ref(false)
</script>

<template>
  <UDrawer
    v-model:open="open"
    title="Drawer with footer"
    description="This is useful when you want a form in a Drawer."
    :ui="{ container: 'max-w-xl mx-auto' }"
  >
    <UButton
      label="Open"
      color="gray"
      variant="subtle"
      trailing-icon="i-heroicons-chevron-up-20-solid"
    />

    <template #body>
      <Placeholder class="h-48" />
    </template>

    <template #footer>
      <UButton label="Submit" color="gray" class="justify-center" />
      <UButton
        label="Cancel"
        color="gray"
        variant="outline"
        class="justify-center"
        @click="open = false"
      />
    </template>
  </UDrawer>
</template>

With command palette

You can use a CommandPalette component inside the Drawer's content.

<script setup lang="ts">
const searchTerm = ref('')

const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
  params: { q: searchTerm },
  transform: (data: { id: number, name: string, email: string }[]) => {
    return data?.map(user => ({ id: user.id, label: user.name, suffix: user.email, avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` } })) || []
  },
  lazy: true
})

const groups = computed(() => [{
  id: 'users',
  label: searchTerm.value ? `Users matching “${searchTerm.value}”...` : 'Users',
  items: users.value || [],
  filter: false
}])
</script>

<template>
  <UDrawer>
    <UButton
      label="Search users..."
      color="gray"
      variant="subtle"
      icon="i-heroicons-magnifying-glass"
    />

    <template #content>
      <UCommandPalette
        v-model:search-term="searchTerm"
        :loading="status === 'pending'"
        :groups="groups"
        placeholder="Search users..."
        class="h-96 border-t border-gray-200 dark:border-gray-800"
      />
    </template>
  </UDrawer>
</template>

API

Props

Prop Default Type
as

'div'

any

The element or component this component should render as.

title

string

description

string

content

Omit<DialogContentProps, "asChild" | "as" | "forceMount">

The content of the drawer.

overlay

true

boolean

Render an overlay behind the drawer.

handle

true

boolean

Render a handle on the drawer.

portal

true

boolean

Render the drawer in a portal.

open

boolean

defaultOpen

boolean

modal

boolean

activeSnapPoint

null | string | number

closeThreshold

number

direction

"bottom"

"left" | "right" | "top" | "bottom"

dismissible

boolean

fadeFromIndex

number

fixed

boolean

nested

boolean

scrollLockTimeout

number

shouldScaleBackground

boolean

snapPoints

(string | number)[]

ui

Partial<{ overlay: string; content: string; handle: string; container: string; header: string; title: string; description: string; body: string; footer: string; }>

Slots

Slot Type
default

{}

handle

{}

content

{}

header

{}

title

{}

description

{}

body

{}

footer

{}

Emits

Event Type
close

[]

drag

[percentageDragged: number]

update:open

[open: boolean]

release

[open: boolean]

update:activeSnapPoint

[val: string | number]

Theme

app.config.ts
export default defineAppConfig({
  ui: {
    drawer: {
      slots: {
        overlay: 'fixed inset-0 z-50 bg-gray-200/75 dark:bg-gray-800/75',
        content: 'fixed z-50 bg-white dark:bg-gray-900 ring ring-gray-200 dark:ring-gray-800 flex focus:outline-none',
        handle: 'shrink-0 rounded-full bg-gray-200 dark:bg-gray-700',
        container: 'w-full flex flex-col gap-4 p-4 overflow-y-auto',
        header: '',
        title: 'text-gray-900 dark:text-white font-semibold',
        description: 'mt-1 text-gray-500 dark:text-gray-400 text-sm',
        body: 'flex-1',
        footer: 'flex flex-col gap-1.5'
      },
      variants: {
        direction: {
          top: {
            content: 'top-0 mb-24 flex-col-reverse rounded-b-lg',
            handle: 'mb-4'
          },
          right: {
            content: 'right-4 flex-row',
            handle: 'ml-4'
          },
          bottom: {
            content: 'bottom-0 mt-24 flex-col rounded-t-lg',
            handle: 'mt-4'
          },
          left: {
            content: 'left-4 flex-row-reverse',
            handle: 'mr-4'
          }
        }
      },
      compoundVariants: [
        {
          direction: [
            'top',
            'bottom'
          ],
          class: {
            content: 'inset-x-0 h-auto max-h-[96%]',
            handle: 'w-12 h-1.5 mx-auto'
          }
        },
        {
          direction: [
            'right',
            'left'
          ],
          class: {
            content: 'inset-y-4 w-auto max-w-[calc(100%-2rem)] rounded-lg after:hidden',
            handle: 'h-12 w-1.5 mt-auto mb-auto'
          }
        }
      ]
    }
  }
})