<script setup lang="ts">
import {
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerHandle,
DrawerOverlay,
DrawerPortal,
DrawerRoot,
DrawerTitle,
DrawerTrigger,
} from 'akar';
import './styles.css';
</script>
<template>
<div class="akar-demo akar-demo-Drawer">
<DrawerRoot>
<DrawerTrigger class="Button grass">
Open Drawer
</DrawerTrigger>
<DrawerPortal>
<DrawerOverlay class="DrawerOverlay" />
<DrawerContent class="DrawerContent">
<DrawerHandle class="DrawerHandle" />
<div class="DrawerBody">
<DrawerTitle class="DrawerTitle">
Edit profile
</DrawerTitle>
<DrawerDescription class="DrawerDescription">
Make changes to your profile here. Swipe down or click close when you're done.
</DrawerDescription>
<fieldset class="Fieldset">
<label
class="Label"
for="name"
> Name </label>
<input
id="name"
class="Input"
value="Pedro Duarte"
>
</fieldset>
<div class="DrawerFooter">
<DrawerClose as-child>
<button class="Button green">
Save changes
</button>
</DrawerClose>
</div>
</div>
</DrawerContent>
</DrawerPortal>
</DrawerRoot>
</div>
</template>@import '../_colors/black-alpha.css';
@import '../_colors/green.css';
@import '../_colors/mauve.css';
@import '../_colors/grass.css';
@scope (.akar-demo-Drawer) {
/* reset */
button,
fieldset,
input {
all: unset;
}
.DrawerOverlay {
background-color: var(--black-a9);
position: fixed;
inset: 0;
z-index: 30;
}
.DrawerOverlay[data-state='open'] {
animation: drawerOverlayShow 450ms cubic-bezier(0.32, 0.72, 0, 1);
}
.DrawerOverlay[data-state='closed'] {
animation: drawerOverlayHide 450ms cubic-bezier(0.32, 0.72, 0, 1);
}
.DrawerContent {
/* Extra drawer area that hangs below the viewport edge, kept off-screen by an
equal negative margin. Pulling the drawer away from its edge slides the
bleed into view instead of exposing the overlay behind it, so the drawer
stretches rather than visually detaching while it rubber-bands. */
--bleed: 48px;
background-color: white;
border-radius: 16px 16px 0 0;
position: fixed;
inset-inline: 0;
bottom: 0;
margin-inline: auto;
margin-bottom: calc(-1 * var(--bleed));
padding-bottom: calc(env(safe-area-inset-bottom, 0px) + var(--bleed));
max-width: 500px;
display: flex;
flex-direction: column;
outline: none;
z-index: 100;
/* `--drawer-swipe-movement-y` is written by DrawerContent while dragging. */
transform: translateY(var(--drawer-swipe-movement-y, 0px));
transition: transform 450ms cubic-bezier(0.32, 0.72, 0, 1);
will-change: transform;
}
/* Enter/exit keyframes animate the independent `translate` property so they
compose with the inline `transform` carrying the live drag offset. */
.DrawerContent[data-state='open'] {
animation: drawerSlideBottomIn 450ms cubic-bezier(0.32, 0.72, 0, 1);
}
.DrawerContent[data-state='closed'] {
animation: drawerSlideBottomOut 450ms cubic-bezier(0.32, 0.72, 0, 1);
}
/* Silence the transform transition during an active drag so it tracks the
pointer in real time. */
.DrawerContent[data-swiping] {
transition-duration: 0ms;
user-select: none;
}
.DrawerHandle {
width: 48px;
height: 6px;
margin: 12px auto 0;
flex-shrink: 0;
border-radius: 9999px;
background-color: var(--mauve-6);
}
.DrawerBody {
padding: 24px;
}
.DrawerTitle {
margin: 0;
color: var(--mauve-12);
font-size: 17px;
font-weight: 600;
}
.DrawerDescription {
margin: 10px 0 20px;
color: var(--mauve-11);
font-size: 15px;
line-height: 1.5;
}
.DrawerFooter {
display: flex;
margin-top: 25px;
justify-content: flex-end;
}
.Fieldset {
display: flex;
gap: 20px;
align-items: center;
margin-bottom: 15px;
}
.Label {
width: 90px;
text-align: right;
font-size: 15px;
color: var(--grass-11);
}
.Input {
width: 100%;
flex: 1;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 8px;
padding: 0 10px;
height: 35px;
font-size: 15px;
line-height: 1;
color: var(--grass-11);
background-color: #fafafa;
box-shadow: 0 0 0 1px var(--green-7);
}
.Input:focus {
box-shadow: 0 0 0 2px var(--green-8);
}
.Button {
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 6px;
padding: 0 15px;
font-size: 15px;
line-height: 1;
height: 35px;
font-weight: 600;
cursor: pointer;
}
.Button.grass {
background-color: white;
color: var(--grass-11);
box-shadow: 0 2px 10px var(--black-a7);
}
.Button.grass:hover {
background-color: var(--mauve-3);
}
.Button.green {
background-color: var(--green-4);
color: var(--green-11);
}
.Button.green:hover {
background-color: var(--green-5);
}
@keyframes drawerOverlayShow {
from {
opacity: 0;
}
}
@keyframes drawerOverlayHide {
to {
opacity: 0;
}
}
/* The bleed already sits below the viewport edge, so the drawer only has to
travel `height - bleed` to clear it. Translating a full 100% would overshoot
by the bleed and make the slide look faster than it is. */
@keyframes drawerSlideBottomIn {
from {
translate: 0 calc(100% - var(--bleed));
}
}
@keyframes drawerSlideBottomOut {
to {
translate: 0 calc(100% - var(--bleed));
}
}
}Features
- Slides in from any edge — bottom, top, left or right.
- Swipe-to-dismiss with momentum, plus optional swipe-to-open.
- Supports snap points for partially-open resting positions.
- Modal, focus-trapped non-modal, and fully non-modal modes.
- Can be controlled or uncontrolled.
- Manages screen reader announcements with
TitleandDescriptioncomponents. - Esc closes the component automatically.
Installation
Install the component from your command line.
pnpm add akar
yarn add akar
npm install akar
bun add akar
Anatomy
Import all parts and piece them together.
<script setup>
import {
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerHandle,
DrawerOverlay,
DrawerPortal,
DrawerRoot,
DrawerTitle,
DrawerTrigger,
} from 'akar';
</script>
<template>
<DrawerRoot>
<DrawerTrigger />
<DrawerPortal>
<DrawerOverlay />
<DrawerContent>
<DrawerHandle />
<DrawerTitle />
<DrawerDescription />
<DrawerClose />
</DrawerContent>
</DrawerPortal>
</DrawerRoot>
</template>
Animating the drawer
The Drawer is unstyled, so the enter/exit transitions and the swipe gesture are
driven entirely by your CSS. DrawerContent exposes the drag offset through CSS
custom properties so you can wire up the live transform:
--drawer-swipe-movement-y— vertical drag offset (for top/bottom drawers)--drawer-swipe-movement-x— horizontal drag offset (for left/right drawers)--drawer-snap-point-offset— offset of the active snap point, when snap points are used--drawer-swipe-progress—0at rest, approaching1as the drawer is swiped away
.DrawerContent {
/* Follow the pointer while dragging. */
transform: translateY(var(--drawer-swipe-movement-y, 0px));
transition: transform 450ms cubic-bezier(0.32, 0.72, 0, 1);
}
/* Use the independent `translate` property for the enter/exit keyframes so they
compose with the `transform` above instead of clobbering the drag offset. */
.DrawerContent[data-state='open'] {
animation: slideIn 450ms cubic-bezier(0.32, 0.72, 0, 1);
}
.DrawerContent[data-state='closed'] {
animation: slideOut 450ms cubic-bezier(0.32, 0.72, 0, 1);
}
/* Cut the transition while actively dragging so the drawer tracks the pointer. */
.DrawerContent[data-swiping] {
transition-duration: 0ms;
}
@keyframes slideIn {
from {
translate: 0 100%;
}
}
@keyframes slideOut {
to {
translate: 0 100%;
}
}
Bleeding past the edge
Dragging a drawer away from the edge it is anchored to doesn't dismiss it — the gesture is damped and the drawer rubber-bands back on release. Without extra room the drawer lifts clean off the edge during that pull and the overlay shows through the gap.
Give the drawer some bleed: extra area past its anchored edge, held off-screen by an equal negative margin. Pulling the drawer now slides the bleed into view instead of the overlay, so it reads as a stretch rather than a detachment.
.DrawerContent {
--bleed: 48px;
padding-bottom: calc(env(safe-area-inset-bottom, 0px) + var(--bleed));
margin-bottom: calc(-1 * var(--bleed));
}
/* The bleed already sits below the viewport edge, so the drawer only has to
travel `height - bleed` to clear it. */
@keyframes slideIn {
from {
translate: 0 calc(100% - var(--bleed));
}
}
@keyframes slideOut {
to {
translate: 0 calc(100% - var(--bleed));
}
}
For a side drawer, swap the axis and point the bleed at the edge the drawer is anchored to — a right-anchored drawer bleeds right, a left-anchored one bleeds left:
/* Anchored right */
.DrawerContent {
padding-inline-end: calc(24px + var(--bleed));
margin-inline-end: calc(-1 * var(--bleed));
}
@keyframes slideIn {
from {
translate: calc(100% - var(--bleed)) 0;
}
}
/* Anchored left */
.DrawerContent {
padding-inline-start: calc(24px + var(--bleed));
margin-inline-start: calc(-1 * var(--bleed));
}
@keyframes slideIn {
from {
translate: calc(-100% + var(--bleed)) 0;
}
}
Soft keyboard
On mobile the software keyboard slides over the bottom of the viewport without resizing it, so a bottom-anchored drawer — and the field the user just tapped — end up underneath it.
Wrap the drawer in DrawerVirtualKeyboardProvider to make it react. It requires
a DrawerViewport around DrawerContent: the viewport is the measurement root
and hosts the keyboard variable, which inherits into the popup and everything in
it.
<template>
<DrawerRoot>
<DrawerVirtualKeyboardProvider>
<DrawerTrigger />
<DrawerPortal>
<DrawerOverlay />
<DrawerViewport>
<DrawerContent>
<input>
</DrawerContent>
</DrawerViewport>
</DrawerPortal>
</DrawerVirtualKeyboardProvider>
</DrawerRoot>
</template>
While a field inside the drawer is focused, the provider scrolls it into the band
left visible above the keyboard and publishes the keyboard's height as
--drawer-keyboard-inset on the viewport.
- Keep the popup frame stable — put header and footer content outside a plain scrollable body so only the body moves.
- Lift a pinned footer — offset it by
var(--drawer-keyboard-inset, 0px), either as padding or as part of its height. - Always include the
0pxfallback — the variable is only set while the keyboard is aligned, so a barevar(--drawer-keyboard-inset)is invalid before the first alignment and after cleanup.
.DrawerFooter {
padding-bottom: calc(1rem + var(--drawer-keyboard-inset, 0px));
}
To lift the whole sheet instead, give up the keyboard's height so its top stays on screen:
.DrawerContent {
height: calc(90dvh - var(--drawer-keyboard-inset, 0px));
max-height: calc(90dvh - var(--drawer-keyboard-inset, 0px));
}
Alignment is suspended while the viewport is pinch-zoomed, and viewport changes under 60px are treated as browser chrome rather than the keyboard.
API Reference
Root
Contains all the parts of a drawer. Manages open state, modality, swipe direction and snap points, and provides the context consumed by every other part.
| Prop | Default | Type |
|---|---|---|
defaultOpen | false | boolean |
defaultSnapPoint | null | string | number | |
modal | true | false | true | "trap-focus"Modality of the drawer.
|
open | booleanv-model:open | |
snapPoint | null | string | numberv-model:snapPoint | |
snapPoints | DrawerSnapPoint[]Preset snap positions (fractions 0-1, pixels >1, or '148px'/'30rem' strings) | |
snapToSequentialPoints | false | booleanWhen true, snaps to the next sequential snap point (one step at a time). When false, snaps to the nearest snap point by distance. |
swipeDirection | "down" | "right" | "left" | "down" | "up"Direction to swipe to dismiss. |
| Event | Payload |
|---|---|
update:open | value: boolean, details?: DrawerOpenChangeDetails |
update:openComplete | value: boolean |
update:snapPoint | value: DrawerSnapPoint | null |
| Slot (default) | Payload |
|---|---|
open | boolean |
close | (): void |
Trigger
The button that opens the drawer.
| Prop | Default | Type |
|---|---|---|
as | "button" | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
| Data attribute | Value |
|---|---|
[data-state] | "open" | "closed" |
Portal
When used, portals your overlay and content parts into the body.
| Prop | Default | Type |
|---|---|---|
defer | booleanDefer the resolving of a Teleport target until other parts of the application have mounted (requires Vue 3.5.0+) | |
disabled | booleanDisable teleport and render the component inline | |
forceMount | booleanUsed to force mounting when more control is needed. Useful when controlling animation with Vue animation libraries. | |
to | string | HTMLElementVue native teleport component prop |
Overlay
A layer that covers the inert portion of the view when the drawer is open. Only
rendered when the drawer is modal.
| Prop | Default | Type |
|---|---|---|
as | "div" | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
forceMount | false | booleanKeep mounted for animation control. |
forceRender | false | booleanRender even when inside a nested drawer. |
| Data attribute | Value |
|---|---|
[data-state] | "open" | "closed" |
[data-swipe-direction] | "up" | "down" | "left" | "right" |
[data-swiping] | "Present when the drawer is being dragged" |
Content
Contains the content to be rendered in the open drawer. Owns the swipe gesture
and exposes the drag offset through CSS custom properties (see Animating the
drawer). Also aliased as DrawerPopup for Base UI parity.
| Prop | Default | Type |
|---|---|---|
as | "div" | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
disableOutsidePointerEvents | booleanWhen | |
finalFocus | true | boolean | HTMLElement | nullFinal focus target when the drawer closes.
|
forceMount | boolean | |
initialFocus | true | boolean | HTMLElement | nullInitial focus target when the drawer opens.
|
| Event | Payload |
|---|---|
closeAutoFocus | event: Event |
escapeKeyDown | event: KeyboardEventEvent handler called when the escape key is down. Can be prevented. |
focusOutside | event: FocusOutsideEventEvent handler called when the focus moves outside of the |
interactOutside | event: PointerDownOutsideEvent | FocusOutsideEventEvent handler called when an interaction happens outside the |
openAutoFocus | event: Event |
pointerDownOutside | event: PointerDownOutsideEventEvent handler called when a |
| Data attribute | Value |
|---|---|
[data-state] | "open" | "closed" |
[data-swipe-direction] | "up" | "down" | "left" | "right" |
[data-swiping] | "Present while the drawer is being dragged" |
[data-nested-drawer-open] | "Present when a nested drawer is open" |
Close
The button that closes the drawer.
| Prop | Default | Type |
|---|---|---|
as | "button" | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
Title
An accessible title to be announced when the drawer is opened.
If you want to hide the title, wrap it inside our Visually Hidden utility like this <VisuallyHidden asChild>.
| Prop | Default | Type |
|---|---|---|
as | "h2" | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
Description
An optional accessible description to be announced when the drawer is opened.
If you want to hide the description, wrap it inside our Visually Hidden utility like this <VisuallyHidden asChild>. If you want to remove the description entirely, remove this part and pass :aria-describedby="undefined" to DrawerContent.
| Prop | Default | Type |
|---|---|---|
as | "p" | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
Handle
A visual grab handle that hints the drawer can be dragged. It is purely
decorative (aria-hidden) — the whole content is draggable regardless.
| Prop | Default | Type |
|---|---|---|
as | "div" | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
| Data attribute | Value |
|---|---|
[data-state] | "open" | "closed" |
SwipeArea
An off-screen edge area that lets users swipe the drawer open. By default it
listens on the opposite side of the Root's swipeDirection.
| Prop | Default | Type |
|---|---|---|
as | "div" | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
disabled | false | booleanDisable swipe-to-open. |
swipeDirection | "right" | "left" | "down" | "up"Override the open swipe direction (defaults to opposite of Root's swipeDirection). |
| Data attribute | Value |
|---|---|
[data-state] | "open" | "closed" |
[data-swipe-direction] | "up" | "down" | "left" | "right" |
Viewport
An optional wrapper around DrawerContent. Mirrors Base UI's Drawer.Viewport
and carries a data-drawer-viewport attribute for downstream selectors.
Required when using VirtualKeyboardProvider, which measures the keyboard
against it.
| Prop | Default | Type |
|---|---|---|
as | "div" | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
VirtualKeyboardProvider
Makes the drawer react to the software keyboard: it keeps the focused field
visible and exposes --drawer-keyboard-inset on DrawerViewport, which it
requires (see Soft keyboard). Takes no props and renders no
markup of its own.
Indent
Wraps page content that should visually shift (scale/indent) as the drawer is
swiped, mimicking the native iOS "card stack" effect. Reads the visual state
from a parent DrawerProvider and syncs the --drawer-swipe-progress and
--drawer-height CSS variables onto its element.
| Prop | Default | Type |
|---|---|---|
as | "div" | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
| Data attribute | Value |
|---|---|
[data-active] | "Present when a drawer is open" |
[data-inactive] | "Present when no drawer is open" |
IndentBackground
The backdrop layer revealed behind an indented page (typically a solid colour
that peeks out as the page scales down). Companion to Indent.
| Prop | Default | Type |
|---|---|---|
as | "div" | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
| Data attribute | Value |
|---|---|
[data-active] | "Present when a drawer is open" |
[data-inactive] | "Present when no drawer is open" |
Examples
Choosing a side
DrawerRoot slides up from the bottom by default. Set swipeDirection to change
the edge the drawer attaches to and the direction users swipe to dismiss it.
<template>
<DrawerRoot swipe-direction="right">
<DrawerTrigger>Open</DrawerTrigger>
<DrawerPortal>
<DrawerOverlay />
<DrawerContent>...</DrawerContent>
</DrawerPortal>
</DrawerRoot>
</template>
/* A right-anchored drawer slides along the X axis instead. */
.DrawerContent {
position: fixed;
inset-block: 0;
right: 0;
width: 20rem;
transform: translateX(var(--drawer-swipe-movement-x, 0px));
transition: transform 450ms cubic-bezier(0.32, 0.72, 0, 1);
}
.DrawerContent[data-state='open'] {
animation: slideInRight 450ms cubic-bezier(0.32, 0.72, 0, 1);
}
.DrawerContent[data-state='closed'] {
animation: slideOutRight 450ms cubic-bezier(0.32, 0.72, 0, 1);
}
.DrawerContent[data-swiping] {
transition-duration: 0ms;
}
@keyframes slideInRight {
from {
translate: 100% 0;
}
}
@keyframes slideOutRight {
to {
translate: 100% 0;
}
}
Snap points
Provide snapPoints to give the drawer intermediate resting positions. Each point
is a fraction (0–1), a pixel value (> 1), or a string like '148px' /
'30rem'. Use v-model:snapPoint to read or control the active one.
<script setup>
import { DrawerContent, DrawerOverlay, DrawerPortal, DrawerRoot, DrawerTrigger } from 'akar'
import { ref } from 'vue'
const snapPoints = [0.4, 0.75, 1]
const activeSnapPoint = ref(0.4)
</script>
<template>
<DrawerRoot
v-model:snap-point="activeSnapPoint"
:snap-points="snapPoints"
>
<DrawerTrigger>Open</DrawerTrigger>
<DrawerPortal>
<DrawerOverlay />
<DrawerContent>...</DrawerContent>
</DrawerPortal>
</DrawerRoot>
</template>
Non-modal drawer
By default the drawer is modal: it traps focus, locks scroll, and dismisses on
outside press. Set modal to 'trap-focus' to keep the focus trap while still
allowing interaction with the rest of the page (no overlay is rendered), or to
false for a fully non-modal panel.
<template>
<DrawerRoot :modal="false">
<DrawerTrigger>Open</DrawerTrigger>
<DrawerPortal>
<DrawerContent>...</DrawerContent>
</DrawerPortal>
</DrawerRoot>
</template>
Reacting to why the drawer closed
The update:open event carries a details object whose reason tells you what
triggered the change — useful for distinguishing a deliberate close from a swipe.
<script setup>
import { DrawerContent, DrawerOverlay, DrawerPortal, DrawerRoot, DrawerTrigger } from 'akar'
function onOpenChange(open, details) {
if (!open && details?.reason === 'swipe') {
// user flicked the drawer away
}
}
</script>
<template>
<DrawerRoot @update:open="onOpenChange">
<DrawerTrigger>Open</DrawerTrigger>
<DrawerPortal>
<DrawerOverlay />
<DrawerContent>...</DrawerContent>
</DrawerPortal>
</DrawerRoot>
</template>
Possible reasons are swipe, escape-key, outside-press, click, cancel,
trigger-press and close-press.
Close using slot props
DrawerRoot exposes open and close via its default slot, so you can close the
drawer programmatically from anywhere inside it.
<template>
<DrawerRoot v-slot="{ close }">
<DrawerTrigger>Open</DrawerTrigger>
<DrawerPortal>
<DrawerOverlay />
<DrawerContent>
<form @submit.prevent="close">
<!-- some inputs -->
<button type="submit">
Save
</button>
</form>
</DrawerContent>
</DrawerPortal>
</DrawerRoot>
</template>
Accessibility
Adheres to the Dialog WAI-ARIA design pattern.
Keyboard Interactions
| Key | Description |
|---|---|
| Space | Opens/closes the drawer. |
| Enter | Opens/closes the drawer. |
| Tab | Moves focus to the next focusable element. |
| Shift + Tab | Moves focus to the previous focusable element. |
| Esc | Closes the drawer and moves focus to DrawerTrigger. |
Custom APIs
Create your own API by abstracting the primitive parts into your own component.
Abstract the overlay and the portal
This example abstracts the DrawerPortal and DrawerOverlay parts.
Usage
<script setup>
import { Drawer, DrawerContent, DrawerTrigger } from './your-drawer';
</script>
<template>
<Drawer>
<DrawerTrigger>Drawer trigger</DrawerTrigger>
<DrawerContent>Drawer Content</DrawerContent>
</Drawer>
</template>
Implementation
export { DrawerRoot as Drawer, DrawerTrigger } from 'akar';
// your-drawer.ts
export { default as DrawerContent } from 'DrawerContent.vue';
<!-- DrawerContent.vue -->
<script setup lang="ts">
import type { DrawerContentEmits, DrawerContentProps } from 'akar';
import { DrawerContent, DrawerHandle, DrawerOverlay, DrawerPortal, useForwardPropsEmits } from 'akar';
const props = defineProps<DrawerContentProps>();
const emits = defineEmits<DrawerContentEmits>();
const forwarded = useForwardPropsEmits(props, emits);
</script>
<template>
<DrawerPortal>
<DrawerOverlay />
<DrawerContent v-bind="forwarded">
<DrawerHandle />
<slot />
</DrawerContent>
</DrawerPortal>
</template>