Drawer
A bottom sheet that slides up and can be swiped to dismiss — mobile-first.
@garn/ui/drawerDefault
Trigger → bottom drawer with footer actions.
Installation
garn is copy-in — the garn CLI writes this component's source into your project, so you own and can edit it. It resolves any base files and installs the npm dependencies below for you.
npx garn-ui add drawer- Registry
drawer- Source
- drawer.tsx
- Deps
vaul- Registry deps
utils
Import
import { Drawer } from "@garn/ui/drawer";Props
Generated from the component's TypeScript types.
No component-specific props — this is a thin wrapper over its native element.
Styling
Target these data-slots and remap these tokens to restyle without forking the component.
[data-slot="drawer-content"]| Property | Token | Tier |
|---|---|---|
padding | --garn-pad-panel | semantic · air |
headerGap | --garn-gap-field | semantic · air |
Overview
Drawer surfaces a secondary task from the bottom edge on touch/mobile — quick settings, a picker, contextual actions you can swipe away. DrawerContent renders a centered drag handle and the page scales back behind it (shouldScaleBackground), giving the native bottom-sheet feel; DrawerTitle (required) names it. It's the mobile counterpart to a Sheet: on the desktop, an edge Sheet or a Popover usually fits better than a bottom drawer.
Guidelines
Use the drawer where the bottom-sheet gesture belongs — touch. It's tuned for thumbs: swipe-to-dismiss and a drag handle. On pointer-first layouts, prefer a right Sheet or an anchored Popover instead of forcing a bottom sheet.
Keep it short enough to swipe away. A drawer is for a quick, dismissible task; if it needs to fill the screen or scroll heavily, that's really a full page or a Sheet. Pin primary actions in DrawerFooter within thumb reach.
Always render a DrawerTitle and respect dismissal. It's the accessible name (sr-only if visually hidden); swipe-down, the overlay, and Escape all close. Don't trap a destructive, no-undo action behind a gesture that's easy to trigger by accident.
Best practices
- Always include a DrawerTitle (sr-only if you don't want it visible).
- Keep content short — a drawer is a quick, dismissible surface, not a full page.
- Provide an explicit action/close in the footer in addition to swipe-to-dismiss.
- Don't use a drawer for primary desktop layouts — prefer sheet there.
- Don't omit the DrawerTitle.
- Don't pack a long form into a bottom sheet — promote it to a page or dialog.
Content guidelines
- Give the DrawerTitle a short, specific name for the panel.
- Keep the content focused on one task and label actions by outcome.
Accessibility
- Role
dialog- ARIA APG
- dialog (modal)
- Focus
- Focus trapped inside the content; returns to the trigger on close.
Enforceable contracts
Every DrawerContent must contain a DrawerTitle (wrap it in sr-only to hide it visually).
when DrawerContent has no DrawerTitle
Keyboard
| Escape | Close the drawer. |
| TabShift+Tab | Cycle focus within the drawer (focus is trapped). |
- • vaul wraps Radix Dialog — aria-modal, labelled by DrawerTitle, described by DrawerDescription.
- • Swipe/drag down dismisses it; the same close paths (Escape / overlay / DrawerClose) work for keyboard and pointer.
- • Focus moves into the drawer on open and returns to the trigger on close.
- Status
- Stable
- Version
v0.3.0- Introduced
v0.1.0
garn doesn't yet record a per-release version history. This reflects the component's current lifecycle state; full changelog entries will appear once the metadata pipeline emits a lifecycle.history field.