Drawer

A panel that slides in from any edge of the screen. Built on Radix Dialog for focus trap, scroll lock and a11y; tuned for mobile with full-bleed side panels, a bottom-sheet grab handle, and swipe-to-dismiss.

Installation#

# One-time setup (tokens + lib helpers)npx koalaui-cli@latest init# Add this component (its dependencies come along)npx koalaui-cli@latest add drawer

Usage#

usage.tsxTSX
import {  Drawer, DrawerTrigger, DrawerContent, DrawerHeader,  DrawerBody, DrawerFooter, DrawerTitle, DrawerDescription, DrawerClose,} from "@/components/ui/drawer"import { Button } from "@/components/ui/button"export function Example() {  return (    <Drawer>      <DrawerTrigger asChild>        <Button>Open drawer</Button>      </DrawerTrigger>      <DrawerContent side="right">        <DrawerHeader>          <DrawerTitle>Notifications</DrawerTitle>          <DrawerDescription>You're all caught up.</DrawerDescription>        </DrawerHeader>        <DrawerBody>{/* … */}</DrawerBody>        <DrawerFooter>          <DrawerClose asChild>            <Button variant="ghost">Close</Button>          </DrawerClose>          <Button>Mark all read</Button>        </DrawerFooter>      </DrawerContent>    </Drawer>  )}

Sides#

side on DrawerContent picks the edge the panel slides from: top, right (default), bottom, or left. Top and bottom render as sheets with a grab handle; left and right as full-height panels. Every variant animates with the dedicated ease-drawer curve.

Mobile: bottom sheet#

On touch devices the bottom sheet is the native pattern. It rounds its top corners, shows a grab handle, and pads past the home-indicator safe area. It is swipe-to-dismiss by default. Drag the handle or header down and release past ~45% (or flick) to close, handing the exit animation back to Radix. The drag only arms after a few pixels, so taps on the rows still register, and it never starts on the scrollable body, so a drag and a scroll never fight. Disable it with swipeToClose={false}.

A bottom sheet often needs to drill into nested views (an action sheet that opens a sub-panel, a settings menu, a multi-step filter) without ever leaving the sheet. Wrap the pages in DrawerNav and give each a DrawerView: only the active view renders, it enters with a native horizontal push (from the right going forward, the left on back), and the sheet's height animates to fit it. A DrawerNavTrigger drills in; a DrawerNavBack (which renders nothing at the root, so you can place it in a shared header) pops. The stack resets when the sheet closes, and swipe-down still dismisses the whole sheet from any view.

Form panel#

A right-side panel is the desktop home for an edit form; it keeps the underlying page visible for context. Compose labelled Input parts in the DrawerBody and land focus on the first field with onOpenAutoFocus.

A left drawer makes a compact, off-canvas navigation menu for small screens. The DrawerBody scrolls independently while the header and footer stay pinned; each link is wrapped in DrawerClose so picking one dismisses the panel.

Sizes#

size caps the panel's cross-axis extent: the width of left/right panels and the height of top/bottom sheets. Panels stay full-bleed below the cap, so on a phone they fill the screen and on desktop they settle into a panel. Four steps: sm, md (default), lg, xl.

Density#

density tightens padding, gaps and the title for application UI; it re-provides itself to the header, body and footer so every part stays in sync. Best driven once via DensityProvider. See Density.

FAQ#

Both are built on Radix Dialog, but reach for a Drawer when the panel should slide in from a screen edge: a right-side edit form that keeps the page visible, a left off-canvas nav, or a mobile bottom sheet. Use Dialog for a centered, modal confirmation or focused task that does not belong to an edge.