Drawer
A panel that comes in from an edge — four sides, seven animations, and a handle you can push it back out by.
- Source
- 1 file
- Dependencies
- framer-motion
- Playground
- no
Examples
Every variant of the shipped component, live. Switch the layout to read them as a table or one at a time, and open Code for the file behind them.
01Side
<Drawer side="left | right | top | bottom" />02Sizes
<Drawer size="xsmall | small | medium | large | xlarge | xxlarge | full" />03Animation
<Drawer animation="slide | spring | smooth | scale | fade | reveal | none" />04Appearance
<Drawer appearance="default | floating | glass" />05Backdrop
<Drawer backdrop="blur | dim | clean | opaque" />06Swipe
<Drawer side="bottom" swipeToClose swipeThreshold={0.35} />07Settings panel
<Drawer title description footer> {scrollingBody} </Drawer>Installation
One package for the whole library — run this once and every component on this site is importable. Adding the tokens to your stylesheet is the other half; the installation guide covers it.
npm install kipuiimport { Drawer } from "kipui";- Peer dependencies
react 19
react-dom 19
tailwindcss 4
- Bundled dependencies
framer-motion
Usage
drawer-example.tsx
import { Drawer } from "kipui";
<Drawer
open={open}
onClose={() => setOpen(false)}
side="right"
title="Workspace settings"
description="Changes apply to everyone in Acme Inc."
footer={<Button onClick={save}>Save changes</Button>}
>
{fields}
</Drawer>- Same contract as Dialog — focus is trapped while open and returned on close, Esc and the backdrop dismiss, and the page behind is locked without its scrollbar disappearing.
- Sizes are widths on `left`/`right` and ceilings on `top`/`bottom`: a sheet is only as tall as its content until it reaches the cap, then its body scrolls.
- Swiping starts on the panel's chrome — handle, header, footer, padding — and never inside the scrolling body or on a control, so it can't steal a scroll or swallow a click. It is off for `fade`, `reveal` and `none`, which have no travel to drag along.
- `prefers-reduced-motion` drops every animation to a short fade and turns swiping off; nothing else about the drawer changes.
- A native page scrollbar is painted by the browser above every element and outside the stacking order, so no overlay can cover it at any z-index. Mount `<PageScrollbar />` once near your root and the page's bar becomes an ordinary element that the drawer covers like anything else — without it ever being hidden and restored.
- `scaleBackground` transforms the one element marked `data-drawer-wrapper` and does nothing if there isn't one — a drawer shouldn't decide on its own that it may transform your layout root.
API reference
Transcribed from the component's own types. Props with no default are required.
| Prop | Default | Description |
|---|---|---|
| openboolean | false | Controlled open state. |
| onClose() => void | required | Fires on Esc, backdrop click, the close button and a completed swipe. |
| side"left" | "right" | "top" | "bottom" | "right" | Edge the panel is anchored to and travels from. |
| size"xsmall" | "small" | "medium" | "large" | "xlarge" | "xxlarge" | "full" | "medium" | Width on the two side panels, height ceiling on the two sheets. |
| animation"slide" | "spring" | "smooth" | "scale" | "fade" | "reveal" | "none" | "slide" | Enter and exit motion. `reveal` is a clip-path wipe — the panel never moves. |
| appearance"default" | "floating" | "glass" | "default" | Flush to the edge, inset with a gap, or translucent. |
| backdrop"blur" | "dim" | "clean" | "opaque" | "blur" | Treatment behind the panel. |
| durationnumber | 300 | Enter and exit duration in ms. Ignored by `spring` and `scale`, which are springs. |
| zIndexnumber | 1400 | Stacking order. Defaults to the `--z-modal` rung of the ladder. |
| closeOnEscboolean | true | Esc dismisses. |
| closeOnBackdropClickboolean | true | Clicking outside dismisses — including the gap around a floating panel. |
| showCloseButtonboolean | true | Close button in the header row. |
| preventScrollboolean | true | Locks the page behind while open. Its scrollbar stays on screen at full width, so nothing appears or disappears on open. |
| initialFocusRefObject<HTMLElement | null> | required | Element focused on open. Defaults to the panel. |
| titleReactNode | required | Heading in the panel, wired to `aria-labelledby`. |
| descriptionReactNode | required | Line under the title, wired to `aria-describedby`. |
| footerReactNode | required | Actions row pinned below the scrolling body. |
| swipeToCloseboolean | true | Drag the panel back out to close it. Ignored by the animations with no travel. |
| swipeThresholdnumber | 0.35 | Fraction of the panel that must be dragged away to close it. A flick closes regardless. |
| handleboolean | required | The grab bar. Defaults to on for `top`/`bottom` when swiping is possible. |
| scaleBackgroundboolean | false | Shrinks the element marked `data-drawer-wrapper` behind the panel, iOS-sheet style. |
| overlayClassNamestring | required | Classes for the overlay that holds the backdrop and the panel. |
| contentClassNamestring | required | Classes for the padded wrapper inside the scrolling body. |