Skip to content

Drawer

A modal panel that slides in from the left, right, or bottom, with swipe dismissal and accessible focus management.

---
import { Button } from '@bejamas/registry/ui/button';
import { Drawer, DrawerBody, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerTitle } from '@bejamas/registry/ui/drawer';
---
<Drawer side="right">
<Button data-slot="drawer-trigger">Open drawer</Button>
<DrawerContent>
<DrawerHeader><DrawerTitle>Delete workspace?</DrawerTitle><DrawerDescription>12 projects and 3,400 files will be permanently removed. This can't be undone.</DrawerDescription></DrawerHeader>
<DrawerBody class="space-y-2">
<div class="flex h-[300px] items-center justify-center rounded-lg bg-muted text-muted-foreground">Slot area</div>
<div class="flex h-[300px] items-center justify-center rounded-lg bg-muted text-muted-foreground">Slot area</div>
</DrawerBody>
<DrawerFooter><Button data-slot="drawer-close" size="sm">Delete workspace</Button><Button data-slot="drawer-close" variant="secondary" size="sm">Cancel</Button></DrawerFooter>
</DrawerContent>
</Drawer>
Terminal window
bunx bejamas add drawer
---
import { Drawer, DrawerContent, DrawerHeader, DrawerTitle, DrawerDescription, DrawerBody, DrawerFooter } from '@bejamas/registry/ui/drawer';
import { Button } from '@bejamas/registry/ui/button';
---
<Drawer side="right">
<Button data-slot="drawer-trigger">Open drawer</Button>
<DrawerContent>
<DrawerHeader><DrawerTitle>Drawer title</DrawerTitle><DrawerDescription>Drawer description.</DrawerDescription></DrawerHeader>
<DrawerBody>Drawer content</DrawerBody>
<DrawerFooter><Button data-slot="drawer-close" variant="secondary">Close</Button></DrawerFooter>
</DrawerContent>
</Drawer>
Prop Type Default
side "left" | "right" | "bottom" "right"
defaultOpen boolean false
modal boolean true
disablePointerDismissal boolean false
closeOnEscape boolean true
class string ""
---
import { Button } from '@bejamas/registry/ui/button';
import { Drawer, DrawerBody, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerTitle } from '@bejamas/registry/ui/drawer';
---
<Drawer side="right">
<Button data-slot="drawer-trigger">Open right drawer</Button>
<DrawerContent>
<DrawerHeader><DrawerTitle>Delete workspace?</DrawerTitle><DrawerDescription>12 projects and 3,400 files will be permanently removed. This can't be undone.</DrawerDescription></DrawerHeader>
<DrawerBody class="space-y-2">
<div class="flex h-[300px] items-center justify-center rounded-lg bg-muted text-muted-foreground">Slot area</div>
<div class="flex h-[300px] items-center justify-center rounded-lg bg-muted text-muted-foreground">Slot area</div>
</DrawerBody>
<DrawerFooter><Button data-slot="drawer-close" size="sm">Delete workspace</Button><Button data-slot="drawer-close" variant="secondary" size="sm">Cancel</Button></DrawerFooter>
</DrawerContent>
</Drawer>
---
import { Button } from '@bejamas/registry/ui/button';
import { Drawer, DrawerBody, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerTitle } from '@bejamas/registry/ui/drawer';
---
<Drawer side="left">
<Button data-slot="drawer-trigger">Open left drawer</Button>
<DrawerContent>
<DrawerHeader><DrawerTitle>Delete workspace?</DrawerTitle><DrawerDescription>12 projects and 3,400 files will be permanently removed. This can't be undone.</DrawerDescription></DrawerHeader>
<DrawerBody class="space-y-2">
<div class="flex h-[300px] items-center justify-center rounded-lg bg-muted text-muted-foreground">Slot area</div>
<div class="flex h-[300px] items-center justify-center rounded-lg bg-muted text-muted-foreground">Slot area</div>
</DrawerBody>
<DrawerFooter><Button data-slot="drawer-close" size="sm">Delete workspace</Button><Button data-slot="drawer-close" variant="secondary" size="sm">Cancel</Button></DrawerFooter>
</DrawerContent>
</Drawer>
---
import { Button } from '@bejamas/registry/ui/button';
import { Drawer, DrawerBody, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerTitle } from '@bejamas/registry/ui/drawer';
---
<Drawer side="bottom">
<Button data-slot="drawer-trigger">Open bottom drawer</Button>
<DrawerContent>
<DrawerHeader><DrawerTitle>Delete workspace?</DrawerTitle><DrawerDescription>12 projects and 3,400 files will be permanently removed. This can't be undone.</DrawerDescription></DrawerHeader>
<DrawerBody class="flex h-[300px] items-center justify-center rounded-lg bg-muted text-muted-foreground">Slot area</DrawerBody>
<DrawerFooter><Button data-slot="drawer-close" size="sm">Delete workspace</Button><Button data-slot="drawer-close" variant="secondary" size="sm">Cancel</Button></DrawerFooter>
</DrawerContent>
</Drawer>

The drawer emits custom events on its root element:

Event Detail Description
drawer:beforechange { open, reason, trigger, payload, originalEvent? } Fired before the open state changes; cancel with event.preventDefault() or event.detail.cancel()
drawer:change { open, reason, trigger, payload, originalEvent? } Fired after the open state changes
drawer:change-complete { open: boolean } Fired after the opening or closing transition finishes
<Drawer id="my-drawer">
<Button data-slot="drawer-trigger">Open drawer</Button>
<DrawerContent>
<DrawerHeader><DrawerTitle>Drawer title</DrawerTitle></DrawerHeader>
<DrawerBody>Drawer content</DrawerBody>
<DrawerFooter><Button data-slot="drawer-close">Close</Button></DrawerFooter>
</DrawerContent>
</Drawer>
const drawer = document.getElementById('my-drawer');
drawer.addEventListener('drawer:change', (event) => {
console.log('Is open:', event.detail.open);
});

Dispatch drawer:set on the root to change its open state:

const drawer = document.getElementById('my-drawer');
drawer.dispatchEvent(new CustomEvent('drawer:set', {
detail: { open: true }
}));
drawer.dispatchEvent(new CustomEvent('drawer:set', {
detail: { open: false }
}));

The drawer exposes these attributes for styling and state queries:

Attribute Element Description
data-state drawer, portal, backdrop, viewport, popup Current state (open or closed)
data-swipe-direction drawer, portal, backdrop, viewport, popup Dismissal direction (left, right, or down)
data-popup-open active trigger Present while its drawer is open
data-swiping backdrop, viewport, popup Present during a swipe gesture
data-starting-style, data-ending-style backdrop, popup Present during entry or exit transitions