Experience System

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-system

In 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>

Use this comparison when choosing between Sidebar and Drawer.

AspectSidebarDrawer
PurposeExtends the workspace with additional information or controls.Temporarily displays supplementary content without changing the page layout.
Relationship to pagePart of the page layout.Overlay on top of the page.
PersistenceUsually remains available while the user works.Opened only when needed and dismissed afterwards.
Content visibilityUser 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 behaviorPushes or resizes the main content (depending on implementation).Does not affect page layout. Covers part of the content.
Interaction frequencyFrequent or continuous interaction throughout the workflow.Occasional or one-off interaction.
Mobile behaviorOften transforms into a drawer.Native interaction pattern on mobile.
StateCan remain open across navigation or during long sessions.Usually closes after completing or cancelling the task.
Typical exampleNavigation; filters adjusted repeatedly while exploring data; inspector / properties panel; selection details while editing; layers panel; file explorerCreating a new item; editing object details; viewing additional information; advanced settings; previewing an object; activity history; comments
Basic statementHiding 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.

PropTypeDefault
defaultOpenboolean
openboolean
onOpenChange(open: boolean) => void
modalbooleantrue
containerHTMLElement | nulldocument.body
direction'top' | 'right' | 'bottom' | 'left''bottom'
onAnimationEnd(open: boolean) => void
dismissiblebooleantrue
handleOnlybooleanfalse
repositionInputsbooleantrue

Snap points

Additional Drawer props

PropTypeDefault
snapPoints(number | string)[]
activeSnapPointnumber | string | null
setActiveSnapPoint(snapPoint: number | string | null) => void
fadeFromIndexnumber
snapToSequentialPointbooleanfalse

DrawerTrigger

The control that opens the drawer.

PropTypeDefault
asChildbooleanfalse

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.

PropTypeDefault
asChildbooleanfalse

DrawerContent

The sliding panel. Merges className. Composes DrawerPortal, DrawerOverlay, and Vaul Content—use lower-level DrawerPortal / DrawerOverlay only if you need custom portaling.

PropTypeDefault
asChildbooleanfalse

DrawerClose

The control that closes the drawer.

PropTypeDefault
asChildbooleanfalse

DrawerTitle

Optional accessible title announced when the drawer opens.

PropTypeDefault
asChildbooleanfalse

DrawerDescription

Optional accessible description announced when the drawer opens.

PropTypeDefault
asChildbooleanfalse

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 usually DrawerDescription) inside the sheet for screen reader context.
  • Ensure DrawerClose controls (or an explicit dismiss path) are keyboard-focusable and have visible labels.
  • For draggable sheets, use handleOnly or data-vaul-no-drag so 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.