Experience System
Components

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>

When to use

  • Providing access to locations within an application or MFE (for example application navigation).
  • Context-sensitive actions and information for the page’s main content (for example filters, versioning).

When not to use

  • Simple messages or actions—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.