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="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" />
    <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="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" />
    <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="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" />
    <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="right">
    <UButton label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" />
    <template #content>
      <Placeholder class="min-w-96 min-h-96 size-full m-4" />
    </template>
  </UDrawer>
</template>
Inset
Use the inset prop to inset the Drawer from the edges.
<template>
  <UDrawer direction="right" inset>
    <UButton label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" />
    <template #content>
      <Placeholder class="min-w-96 min-h-96 size-full 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="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" />
    <template #content>
      <Placeholder class="h-48 m-4" />
    </template>
  </UDrawer>
</template>
Handle Only
Use the handle-only prop to only allow the Drawer to be dragged by the handle.
<template>
  <UDrawer handle-only>
    <UButton label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" />
    <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="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" />
    <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. You can set the set-background-color-on-scale prop to false to prevent changing the background color.
<template>
  <UDrawer should-scale-background set-background-color-on-scale>
    <UButton label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" />
    <template #content>
      <Placeholder class="h-48 m-4" />
    </template>
  </UDrawer>
</template>
data-vaul-drawer-wrapper directive to a parent element of your app to make this work.<template>
  <UApp>
    <div class="bg-(--ui-bg)" data-vaul-drawer-wrapper>
      <NuxtLayout>
        <NuxtPage />
      </NuxtLayout>
    </div>
  </UApp>
</template>
export default defineNuxtConfig({
  app: {
    rootAttrs: {
      'data-vaul-drawer-wrapper': '',
      'class': 'bg-(--ui-bg)'
    }
  }
})
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="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" />
    <template #content>
      <Placeholder class="h-48 m-4" />
    </template>
  </UDrawer>
</template>
defineShortcuts, you can toggle the Drawer by pressing O.Prevent closing
Set the dismissible prop to false to prevent the Drawer from being closed when clicking outside of it or pressing escape.
<script setup lang="ts">
const open = ref(false)
</script>
<template>
  <UDrawer
    v-model:open="open"
    :dismissible="false"
    :ui="{ header: 'flex items-center justify-between' }"
  >
    <UButton label="Open" color="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" />
    <template #header>
      <h2 class="text-(--ui-text-highlighted) font-semibold">Drawer non-dismissible</h2>
      <UButton color="neutral" variant="ghost" icon="i-lucide-x" @click="open = false" />
    </template>
    <template #body>
      <Placeholder class="h-48" />
    </template>
  </UDrawer>
</template>
header slot is used to add a close button which is not done by default.With footer slot
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="neutral" variant="subtle" trailing-icon="i-lucide-chevron-up" />
    <template #body>
      <Placeholder class="h-48" />
    </template>
    <template #footer>
      <UButton label="Submit" color="neutral" class="justify-center" />
      <UButton
        label="Cancel"
        color="neutral"
        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', {
  key: 'command-palette-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 || [],
  ignoreFilter: true
}])
</script>
<template>
  <UDrawer :handle="false">
    <UButton
      label="Search users..."
      color="neutral"
      variant="subtle"
      icon="i-lucide-search"
    />
    <template #content>
      <UCommandPalette
        v-model:search-term="searchTerm"
        :loading="status === 'pending'"
        :groups="groups"
        placeholder="Search users..."
        class="h-80"
      />
    </template>
  </UDrawer>
</template>
API
Props
| Prop | Default | Type | 
|---|---|---|
as | 
  | 
 The element or component this component should render as.  | 
title | 
  | |
description | 
  | |
inset | 
  | 
 Whether to inset the drawer from the edges.  | 
content | 
 The content of the drawer. 
  | |
overlay | 
  | 
 Render an overlay behind the drawer.  | 
handle | 
  | 
 Render a handle on the drawer.  | 
portal | 
  | 
 Render the drawer in a portal.  | 
fadeFromIndex | 
 Index of a   | |
open | 
  | |
defaultOpen | 
 Opened by default, skips initial enter animation. Still reacts to   | |
fixed | 
 When   | |
activeSnapPoint | 
  | |
closeThreshold | 
 Number between 0 and 1 that determines when the drawer should be closed. Example: threshold of 0.5 would close the drawer if the user swiped for 50% of the height of the drawer or more.  | |
shouldScaleBackground | 
  | |
setBackgroundColorOnScale | 
 When   | |
scrollLockTimeout | 
 Duration for which the drawer is not draggable after scrolling content inside of the drawer.  | |
dismissible | 
  | 
 When   | 
modal | 
  | 
 When   | 
nested | 
  | |
direction | 
  | 
 Direction of the drawer. Can be   | 
noBodyStyles | 
 When   | |
handleOnly | 
 When   | |
preventScrollRestoration | 
  | |
snapPoints | 
 Array of numbers from 0 to 100 that corresponds to % of the screen a given snap point should take up.
Should go from least visible. Example   | |
ui | 
  | 
Slots
| Slot | Type | 
|---|---|
default | 
  | 
content | 
  | 
header | 
  | 
title | 
  | 
description | 
  | 
body | 
  | 
footer | 
  | 
Emits
| Event | Type | 
|---|---|
close | 
  | 
drag | 
  | 
release | 
  | 
update:open | 
  | 
update:activeSnapPoint | 
  | 
animationEnd | 
  | 
Theme
export default defineAppConfig({
  ui: {
    drawer: {
      slots: {
        overlay: 'fixed inset-0 bg-(--ui-bg-elevated)/75',
        content: 'fixed bg-(--ui-bg) ring ring-(--ui-border) flex focus:outline-none',
        handle: [
          'shrink-0 !bg-(--ui-bg-accented)',
          'transition-opacity'
        ],
        container: 'w-full flex flex-col gap-4 p-4 overflow-y-auto',
        header: '',
        title: 'text-(--ui-text-highlighted) font-semibold',
        description: 'mt-1 text-(--ui-text-muted) text-sm',
        body: 'flex-1',
        footer: 'flex flex-col gap-1.5'
      },
      variants: {
        direction: {
          top: {
            content: 'mb-24 flex-col-reverse',
            handle: 'mb-4'
          },
          right: {
            content: 'flex-row',
            handle: '!ml-4'
          },
          bottom: {
            content: 'mt-24 flex-col',
            handle: 'mt-4'
          },
          left: {
            content: 'flex-row-reverse',
            handle: '!mr-4'
          }
        },
        inset: {
          true: {
            content: 'rounded-[calc(var(--ui-radius)*2)] after:hidden'
          }
        }
      },
      compoundVariants: [
        {
          direction: [
            'top',
            'bottom'
          ],
          class: {
            content: 'h-auto max-h-[96%]',
            handle: '!w-12 !h-1.5 mx-auto'
          }
        },
        {
          direction: [
            'right',
            'left'
          ],
          class: {
            content: 'w-auto max-w-[calc(100%-2rem)]',
            handle: '!h-12 !w-1.5 mt-auto mb-auto'
          }
        },
        {
          direction: 'top',
          inset: true,
          class: {
            content: 'inset-x-4 top-4'
          }
        },
        {
          direction: 'top',
          inset: false,
          class: {
            content: 'inset-x-0 top-0 rounded-b-[calc(var(--ui-radius)*2)]'
          }
        },
        {
          direction: 'bottom',
          inset: true,
          class: {
            content: 'inset-x-4 bottom-4'
          }
        },
        {
          direction: 'bottom',
          inset: false,
          class: {
            content: 'inset-x-0 bottom-0 rounded-t-[calc(var(--ui-radius)*2)]'
          }
        },
        {
          direction: 'left',
          inset: true,
          class: {
            content: 'inset-y-4 left-4'
          }
        },
        {
          direction: 'left',
          inset: false,
          class: {
            content: 'inset-y-0 left-0 rounded-r-[calc(var(--ui-radius)*2)]'
          }
        },
        {
          direction: 'right',
          inset: true,
          class: {
            content: 'inset-y-4 right-4'
          }
        },
        {
          direction: 'right',
          inset: false,
          class: {
            content: 'inset-y-0 right-0 rounded-l-[calc(var(--ui-radius)*2)]'
          }
        }
      ]
    }
  }
})
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '@nuxt/ui/vite'
export default defineConfig({
  plugins: [
    vue(),
    ui({
      ui: {
        drawer: {
          slots: {
            overlay: 'fixed inset-0 bg-(--ui-bg-elevated)/75',
            content: 'fixed bg-(--ui-bg) ring ring-(--ui-border) flex focus:outline-none',
            handle: [
              'shrink-0 !bg-(--ui-bg-accented)',
              'transition-opacity'
            ],
            container: 'w-full flex flex-col gap-4 p-4 overflow-y-auto',
            header: '',
            title: 'text-(--ui-text-highlighted) font-semibold',
            description: 'mt-1 text-(--ui-text-muted) text-sm',
            body: 'flex-1',
            footer: 'flex flex-col gap-1.5'
          },
          variants: {
            direction: {
              top: {
                content: 'mb-24 flex-col-reverse',
                handle: 'mb-4'
              },
              right: {
                content: 'flex-row',
                handle: '!ml-4'
              },
              bottom: {
                content: 'mt-24 flex-col',
                handle: 'mt-4'
              },
              left: {
                content: 'flex-row-reverse',
                handle: '!mr-4'
              }
            },
            inset: {
              true: {
                content: 'rounded-[calc(var(--ui-radius)*2)] after:hidden'
              }
            }
          },
          compoundVariants: [
            {
              direction: [
                'top',
                'bottom'
              ],
              class: {
                content: 'h-auto max-h-[96%]',
                handle: '!w-12 !h-1.5 mx-auto'
              }
            },
            {
              direction: [
                'right',
                'left'
              ],
              class: {
                content: 'w-auto max-w-[calc(100%-2rem)]',
                handle: '!h-12 !w-1.5 mt-auto mb-auto'
              }
            },
            {
              direction: 'top',
              inset: true,
              class: {
                content: 'inset-x-4 top-4'
              }
            },
            {
              direction: 'top',
              inset: false,
              class: {
                content: 'inset-x-0 top-0 rounded-b-[calc(var(--ui-radius)*2)]'
              }
            },
            {
              direction: 'bottom',
              inset: true,
              class: {
                content: 'inset-x-4 bottom-4'
              }
            },
            {
              direction: 'bottom',
              inset: false,
              class: {
                content: 'inset-x-0 bottom-0 rounded-t-[calc(var(--ui-radius)*2)]'
              }
            },
            {
              direction: 'left',
              inset: true,
              class: {
                content: 'inset-y-4 left-4'
              }
            },
            {
              direction: 'left',
              inset: false,
              class: {
                content: 'inset-y-0 left-0 rounded-r-[calc(var(--ui-radius)*2)]'
              }
            },
            {
              direction: 'right',
              inset: true,
              class: {
                content: 'inset-y-4 right-4'
              }
            },
            {
              direction: 'right',
              inset: false,
              class: {
                content: 'inset-y-0 right-0 rounded-l-[calc(var(--ui-radius)*2)]'
              }
            }
          ]
        }
      }
    })
  ]
})
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import uiPro from '@nuxt/ui-pro/vite'
export default defineConfig({
  plugins: [
    vue(),
    uiPro({
      ui: {
        drawer: {
          slots: {
            overlay: 'fixed inset-0 bg-(--ui-bg-elevated)/75',
            content: 'fixed bg-(--ui-bg) ring ring-(--ui-border) flex focus:outline-none',
            handle: [
              'shrink-0 !bg-(--ui-bg-accented)',
              'transition-opacity'
            ],
            container: 'w-full flex flex-col gap-4 p-4 overflow-y-auto',
            header: '',
            title: 'text-(--ui-text-highlighted) font-semibold',
            description: 'mt-1 text-(--ui-text-muted) text-sm',
            body: 'flex-1',
            footer: 'flex flex-col gap-1.5'
          },
          variants: {
            direction: {
              top: {
                content: 'mb-24 flex-col-reverse',
                handle: 'mb-4'
              },
              right: {
                content: 'flex-row',
                handle: '!ml-4'
              },
              bottom: {
                content: 'mt-24 flex-col',
                handle: 'mt-4'
              },
              left: {
                content: 'flex-row-reverse',
                handle: '!mr-4'
              }
            },
            inset: {
              true: {
                content: 'rounded-[calc(var(--ui-radius)*2)] after:hidden'
              }
            }
          },
          compoundVariants: [
            {
              direction: [
                'top',
                'bottom'
              ],
              class: {
                content: 'h-auto max-h-[96%]',
                handle: '!w-12 !h-1.5 mx-auto'
              }
            },
            {
              direction: [
                'right',
                'left'
              ],
              class: {
                content: 'w-auto max-w-[calc(100%-2rem)]',
                handle: '!h-12 !w-1.5 mt-auto mb-auto'
              }
            },
            {
              direction: 'top',
              inset: true,
              class: {
                content: 'inset-x-4 top-4'
              }
            },
            {
              direction: 'top',
              inset: false,
              class: {
                content: 'inset-x-0 top-0 rounded-b-[calc(var(--ui-radius)*2)]'
              }
            },
            {
              direction: 'bottom',
              inset: true,
              class: {
                content: 'inset-x-4 bottom-4'
              }
            },
            {
              direction: 'bottom',
              inset: false,
              class: {
                content: 'inset-x-0 bottom-0 rounded-t-[calc(var(--ui-radius)*2)]'
              }
            },
            {
              direction: 'left',
              inset: true,
              class: {
                content: 'inset-y-4 left-4'
              }
            },
            {
              direction: 'left',
              inset: false,
              class: {
                content: 'inset-y-0 left-0 rounded-r-[calc(var(--ui-radius)*2)]'
              }
            },
            {
              direction: 'right',
              inset: true,
              class: {
                content: 'inset-y-4 right-4'
              }
            },
            {
              direction: 'right',
              inset: false,
              class: {
                content: 'inset-y-0 right-0 rounded-l-[calc(var(--ui-radius)*2)]'
              }
            }
          ]
        }
      }
    })
  ]
})