Components / Overlays & disclosure

Drawer

A temporary side surface for focused work.

Install

npx shadcn@latest add https://irisui.dev/r/drawer.json
import { Drawer } from "@/components/iris/drawer/drawer";
Registry item drawerBrings in foundationRegistry JSON

Preview

Drawer

A temporary panel that slides in from any edge for focused side work, such as editing a record without leaving the list behind it.

When to use

  • Editing or inspecting one item while keeping the page context visible (settings for a row, a filter panel, details).
  • Forms that are too long for a popover but do not need to block the whole task.
  • Not for decisions that must interrupt: use Dialog. On touch layouts where a peek height helps, use BottomSheet.

Usage

import { Drawer } from "@/components/iris/drawer/drawer";

h(
  Drawer,
  null,
  h(DrawerTrigger, { asChild: true }, h(Button, { variant: "secondary" }, "Edit token")),
  h(
    DrawerContent,
    {
      title: "Edit project token",
      description: "Agents use this token to edit the project over MCP and the CLI.",
      side: "right",
    },
    h(Input, { label: "Name", defaultValue: "claude-code@agent" }),
    h(DrawerClose, { asChild: true }, h(Button, { variant: "ghost" }, "Cancel")),
  ),
);

Props

Drawer (root, wraps Radix Dialog Root)

Prop Type Default Description
open boolean — Controlled open state.
defaultOpen boolean false Initial state when uncontrolled.
onOpenChange (open: boolean) => void — Called on open, close, Escape, outside click, or a dismissing drag.
modal boolean true Radix modal behaviour (focus trap, scroll lock).

DrawerContent

Prop Type Default Description
title string — Required. Rendered as the dialog title; changes cross-fade while open.
description string — Optional supporting line under the title.
side "left" | "right" | "top" | "bottom" "right" Edge the panel slides from.
container HTMLElement | null — Portal into this element instead of body. It needs position: relative and overflow: hidden.
children ReactNode — Body content.

DrawerTrigger and DrawerClose are the Radix Dialog Trigger and Close; use asChild to render your own button.

useDrawerItem(item) (Iris.useDrawerItem) returns the last non-null item it was given. For a drawer driven by a selection, pass the selected item (or null when nothing is open) and render the content from the result, so the content stays on screen while the panel slides out:

const { useDrawerItem } = window.Iris;
const shown = useDrawerItem(selectedKey); // selectedKey is null once closed
h(
  Drawer,
  {
    open: selectedKey != null,
    onOpenChange: (open) => {
      if (!open) setSelectedKey(null);
    },
  },
  shown && h(DrawerContent, { title: shown.name }, h(TokenForm, { token: shown })),
);

Notes

  • Built on Radix Dialog: focus is trapped and returned to the trigger, Escape closes, and the title and description are wired to aria-labelledby / aria-describedby. The close button is labelled "Close drawer".
  • The header is a drag handle: dragging past a third of the panel or flicking toward the edge dismisses it; shorter drags spring back.
  • Reduced motion swaps the slide for a short fade and disables dragging.
  • Reads --surface, --hover, --line, --line-strong, --ink, --ink-2, --shadow-floating and --radius-button (the close button).