pnpm dlx dreamy add drawerexport default function BasicDrawer() {
const { isOpen, onClose, onOpen } = useControllable();
return (
<>
<Button variant={"primary"} onClick={onOpen}>
Open Drawer
</Button>
<Drawer.Root isOpen={isOpen} onClose={onClose}>
<Drawer.Overlay />
<Drawer.Content>
<Drawer.Header>Drawer Header</Drawer.Header>
<Drawer.CloseButton />
<Drawer.Body>Drawer Body</Drawer.Body>
<Drawer.Footer>
<Button onClick={onClose}>Close</Button>
</Drawer.Footer>
</Drawer.Content>
</Drawer.Root>
</>
);
}Drawer uses the shared Portal stack at the modal layer (1400), same as Modal . Portalled controls inside it stay above the Drawer automatically.
Drawer has two visual variants. The default is inset, which floats the panel with spacing from the viewport and rounded corners. Use simple for a flush panel that touches the screen edges.
export function VariantDrawers() {
const variants = ["inset", "simple"] as const;
return (
<Flex gap={2} wrapped>
{variants.map((variant) => (
<DrawerVariant key={variant} variant={variant} />
))}
</Flex>
);
}
function DrawerVariant({ variant }: { variant: "inset" | "simple" }) {
const { isOpen, onClose, onOpen } = useControllable();
return (
<>
<Button variant={"primary"} onClick={onOpen}>
Open {variant} Drawer
</Button>
<Drawer.Root isOpen={isOpen} onClose={onClose} variant={variant}>
<Drawer.Overlay />
<Drawer.Content>
<Drawer.Header>{variant} Drawer</Drawer.Header>
<Drawer.CloseButton />
<Drawer.Body>Drawer body</Drawer.Body>
<Drawer.Footer>
<Button onClick={onClose}>Close</Button>
</Drawer.Footer>
</Drawer.Content>
</Drawer.Root>
</>
);
}By default the drawer slides in from the right. You can also place it on the left, top, or bottom.
export function PlacementDrawers() {
const placements = ["right", "left", "top", "bottom"] as const;
return (
<Flex gap={2} wrapped>
{placements.map((placement) => (
<DrawerPlacement key={placement} placement={placement} />
))}
</Flex>
);
}
function DrawerPlacement({
placement
}: {
placement: "right" | "left" | "top" | "bottom";
}) {
const { isOpen, onClose, onOpen } = useControllable();
return (
<>
<Button variant={"primary"} onClick={onOpen}>
Open {placement} Drawer
</Button>
<Drawer.Root isOpen={isOpen} onClose={onClose} placement={placement}>
<Drawer.Overlay />
<Drawer.Content>
<Drawer.Header>{placement} Drawer</Drawer.Header>
<Drawer.CloseButton />
<Drawer.Body>Slides in from the {placement}.</Drawer.Body>
<Drawer.Footer>
<Button onClick={onClose}>Close</Button>
</Drawer.Footer>
</Drawer.Content>
</Drawer.Root>
</>
);
}Control the drawer width (or height for top/bottom placement) with the size prop.
export function SizeDrawers() {
const sizes = ["xs", "sm", "md", "lg", "xl", "2xl", "full"] as const;
return (
<Flex gap={2} wrapped>
{sizes.map((size) => (
<DrawerSize key={size} size={size} />
))}
</Flex>
);
}
function DrawerSize({ size }: { size: "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "full" }) {
const { isOpen, onClose, onOpen } = useControllable();
return (
<>
<Button variant={"primary"} onClick={onOpen}>
Open {size} Drawer
</Button>
<Drawer.Root isOpen={isOpen} onClose={onClose} size={size}>
<Drawer.Overlay />
<Drawer.Content>
<Drawer.Header>{size} Drawer</Drawer.Header>
<Drawer.CloseButton />
<Drawer.Body>This is a {size} drawer!</Drawer.Body>
<Drawer.Footer>
<Button onClick={onClose}>Close</Button>
</Drawer.Footer>
</Drawer.Content>
</Drawer.Root>
</>
);
}Use scrollBehavior="inside" (the default) so long content scrolls within the drawer body while the header and footer stay fixed.
export function ScrollableInsideDrawer() {
const { isOpen, onClose, onOpen } = useControllable();
return (
<>
<Button variant={"primary"} onClick={onOpen}>
Open Drawer
</Button>
<Drawer.Root isOpen={isOpen} onClose={onClose} scrollBehavior="inside" size="lg">
<Drawer.Overlay />
<Drawer.Content>
<Drawer.Header>Drawer Header</Drawer.Header>
<Drawer.CloseButton />
<Drawer.Body>
{/* long content */}
</Drawer.Body>
<Drawer.Footer>
<Button onClick={onClose}>Close</Button>
</Drawer.Footer>
</Drawer.Content>
</Drawer.Root>
</>
);
}