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
02Sizes
03Animation
04Appearance
05Backdrop
06Swipe
07Settings panel

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 kipui
import { 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.

PropDefaultDescription
openbooleanfalseControlled open state.
onClose() => voidrequiredFires 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.
durationnumber300Enter and exit duration in ms. Ignored by `spring` and `scale`, which are springs.
zIndexnumber1400Stacking order. Defaults to the `--z-modal` rung of the ladder.
closeOnEscbooleantrueEsc dismisses.
closeOnBackdropClickbooleantrueClicking outside dismisses — including the gap around a floating panel.
showCloseButtonbooleantrueClose button in the header row.
preventScrollbooleantrueLocks the page behind while open. Its scrollbar stays on screen at full width, so nothing appears or disappears on open.
initialFocusRefObject<HTMLElement | null>requiredElement focused on open. Defaults to the panel.
titleReactNoderequiredHeading in the panel, wired to `aria-labelledby`.
descriptionReactNoderequiredLine under the title, wired to `aria-describedby`.
footerReactNoderequiredActions row pinned below the scrolling body.
swipeToClosebooleantrueDrag the panel back out to close it. Ignored by the animations with no travel.
swipeThresholdnumber0.35Fraction of the panel that must be dragged away to close it. A flick closes regardless.
handlebooleanrequiredThe grab bar. Defaults to on for `top`/`bottom` when swiping is possible.
scaleBackgroundbooleanfalseShrinks the element marked `data-drawer-wrapper` behind the panel, iOS-sheet style.
overlayClassNamestringrequiredClasses for the overlay that holds the backdrop and the panel.
contentClassNamestringrequiredClasses for the padded wrapper inside the scrolling body.