Drawer
A floating panel that slides in from an edge of the viewport—filters, forms, and mobile-friendly sheets.
Installation
The component is exported from @by/experience-system. Add the package with your package manager:
pnpm add @by/experience-systemIn this monorepo, depend on the workspace package (for example via workspace:* or your catalog) so imports resolve to packages/experience-system.
Composition
Use the following composition to build a Drawer:
Drawer
├── DrawerTrigger (optional if controlled)
└── DrawerContent
├── DrawerHeader
│ ├── DrawerTitle
│ └── DrawerDescription (optional)
├── (body)
└── DrawerFooter (optional)
└── DrawerClose (optional)DrawerContent portals with overlay. Optional parts: DrawerHandle (drag affordance), DrawerPortal, DrawerOverlay when you need lower-level control. Built on Vaul; use direction, handleOnly, and data-vaul-no-drag per Vaul docs when content includes sliders or draggable regions.
Usage
import {
Button,
Drawer,
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
} from '@by/experience-system';Drawer and its parts are client components ('use client'). Wrap your tree in ThemeProvider so DrawerContent can resolve the portal container. Use it inside a Client Component or a dynamic import when using the Next.js App Router.
<Drawer>
<DrawerTrigger asChild>
<Button>Open</Button>
</DrawerTrigger>
<DrawerContent>
<DrawerHeader>
<DrawerTitle>Title</DrawerTitle>
<DrawerDescription>Optional description.</DrawerDescription>
</DrawerHeader>
<DrawerFooter>
<DrawerClose asChild>
<Button variant="outline">Close</Button>
</DrawerClose>
</DrawerFooter>
</DrawerContent>
</Drawer>Sidebar vs Drawer
Use this comparison when choosing between Sidebar and Drawer.
| Aspect | Sidebar | Drawer |
|---|---|---|
| Purpose | Extends the workspace with additional information or controls. | Temporarily displays supplementary content without changing the page layout. |
| Relationship to page | Part of the page layout. | Overlay on top of the page. |
| Persistence | Usually remains available while the user works. | Opened only when needed and dismissed afterwards. |
| Content visibility | User is expected to view both the sidebar and the main content simultaneously. | User focuses primarily on the drawer while still keeping some page context visible underneath. |
| Layout behavior | Pushes or resizes the main content (depending on implementation). | Does not affect page layout. Covers part of the content. |
| Interaction frequency | Frequent or continuous interaction throughout the workflow. | Occasional or one-off interaction. |
| Mobile behavior | Often transforms into a drawer. | Native interaction pattern on mobile. |
| State | Can remain open across navigation or during long sessions. | Usually closes after completing or cancelling the task. |
| Typical example | Navigation; filters adjusted repeatedly while exploring data; inspector / properties panel; selection details while editing; layers panel; file explorer | Creating a new item; editing object details; viewing additional information; advanced settings; previewing an object; activity history; comments |
| Basic statement | Hiding this panel would significantly interrupt the user's workflow. | User can complete this task and then continue working without needing the panel to remain visible. |
When to use
Choose Drawer when the panel is temporary, overlays the page, and the user can finish the task and dismiss it:
- Creating or editing an item, viewing object details, or previewing content.
- Advanced settings, activity history, comments, or other supplementary information for a focused task.
- Occasional or one-off interaction where the main layout should stay unchanged underneath.
When not to use
- Persistent navigation or tools the user needs continuously while working—use Sidebar.
- Filters or inspectors adjusted repeatedly alongside the main content—use Sidebar.
- Simple messages or confirmations—use Dialog instead.
Examples
The live preview at the top of this page uses drawer-usage (trigger + sheet shell).
Overview
A left drawer with handleOnly so dragging to dismiss is limited to the handle—useful when the sheet contains sliders or other draggable controls. Add data-vaul-no-drag on elements that must not initiate a sheet drag (see Vaul documentation).
Sides
Use the direction prop on Drawer for top, right, bottom (default), or left. Tune height on top/bottom sheets with className and the data-[vaul-drawer-direction=…] attributes when needed.
API Reference
Subsection titles name @by/experience-system exports and the Vaul part they wrap. Prop names, defaults, and roles follow the Vaul API Reference (Drawer here is Vaul’s Drawer.Root). For extra options on the root (for example shouldScaleBackground, closeThreshold, nested), see the DialogProps surface in the vaul package types.
Drawer
Root. Contains all parts of the drawer.
| Prop | Type | Default |
|---|---|---|
defaultOpen | boolean | — |
open | boolean | — |
onOpenChange | (open: boolean) => void | — |
modal | boolean | true |
container | HTMLElement | null | document.body |
direction | 'top' | 'right' | 'bottom' | 'left' | 'bottom' |
onAnimationEnd | (open: boolean) => void | — |
dismissible | boolean | true |
handleOnly | boolean | false |
repositionInputs | boolean | true |
Snap points
Additional Drawer props
| Prop | Type | Default |
|---|---|---|
snapPoints | (number | string)[] | — |
activeSnapPoint | number | string | null | — |
setActiveSnapPoint | (snapPoint: number | string | null) => void | — |
fadeFromIndex | number | — |
snapToSequentialPoint | boolean | false |
DrawerTrigger
The control that opens the drawer.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
DrawerPortal
When used, portals overlay and content into the target node. Forwards Radix Portal props (for example container, forceMount). In the default composition, DrawerContent already wraps DrawerPortal and sets container from ThemeProvider / useContainerElement() when present.
DrawerOverlay
Layer over the inert portion of the page while the drawer is open. Merges className with Experience System overlay styles. Included inside DrawerContent by default.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
DrawerContent
The sliding panel. Merges className. Composes DrawerPortal, DrawerOverlay, and Vaul Content—use lower-level DrawerPortal / DrawerOverlay only if you need custom portaling.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
DrawerClose
The control that closes the drawer.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
DrawerTitle
Optional accessible title announced when the drawer opens.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
DrawerDescription
Optional accessible description announced when the drawer opens.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
DrawerHandle
Optional drag affordance. Vaul’s public API lists no props; the underlying type may accept options such as preventCycle.
DrawerHeader
Title region; spacing and alignment adapt by direction (Experience System layout wrapper, not a Vaul primitive).
DrawerFooter
Sticky footer row (mt-auto flex column; Experience System layout wrapper).
Accessibility
- Keep
DrawerTitle(and usuallyDrawerDescription) inside the sheet for screen reader context. - Ensure
DrawerClosecontrols (or an explicit dismiss path) are keyboard-focusable and have visible labels. - For draggable sheets, use
handleOnlyordata-vaul-no-dragso nested sliders and inputs remain usable.
Source in the repo: packages/experience-system/src/components/Drawer/Drawer.tsx. Agent-oriented contracts: packages/experience-system/src/components/Drawer/Drawer_instructions.md.