shadcn.io is not affiliated with official shadcn/ui
Installation
bunx --bun shadcn@latest add https://kit.dev/r/base/drawer.jsonnpx shadcn@latest add https://kit.dev/r/base/drawer.jsonpnpm dlx shadcn@latest add https://kit.dev/r/base/drawer.jsonyarn shadcn@latest add https://kit.dev/r/base/drawer.jsonThis component depends on Button. Install it first if you haven't already.
Install the following dependencies:
bun add @base-ui/react tailwind-variantsnpm install @base-ui/react tailwind-variantspnpm add @base-ui/react tailwind-variantsyarn add @base-ui/react tailwind-variantsCopy and paste the following code into your project.
"use client";
import { Drawer as DrawerPrimitive } from "@base-ui/react/drawer";
import type React from "react";
import { createContext, useContext, useMemo } from "react";
import { cn } from "@/lib/utils";
interface DrawerContextProps {
hasSnapPoints: boolean;
modal: DrawerPrimitive.Root.Props["modal"];
showSwipeHandle: boolean;
swipeDirection: NonNullable<DrawerPrimitive.Root.Props["swipeDirection"]>;
}
const DrawerContext = createContext<DrawerContextProps | null>(null);
export const useDrawer = () => {
const context = useContext(DrawerContext);Update the import paths to match your project setup.
Anatomy
DrawerPositioner
DrawerPositioner
DrawerTitle
DrawerDescription
DrawerTrigger
DrawerOverlay
DrawerGrabber
└── DrawerGrabberIndicator
DrawerClose
DrawerSwipeAreaUsage
import { useState } from "react";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Drawer,
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
} from "@/components/ui/drawer";
import {
Field,
FieldContent,
FieldDescription,
FieldLabel,
FieldTitle,
} from "@/components/ui/field";
import {
RadioGroup,
RadioGroupItem,
} from "@/components/ui/radio-group";
import { toast } from "@/components/ui/toast";
import { useIsMobile } from "@/hooks/use-is-mobile";const deliveryTimes = [
{
badge: "Fastest",
description: "25–35 min · Driver assigned now",
id: "delivery-asap",
label: "Standard delivery",
value: "asap",
},
{
description: "Prep starts at 4:45 PM",
id: "delivery-5-00",
label: "5:00 PM – 5:15 PM",
value: "5-00",
},
{
description: "Good if you're heading home",
id: "delivery-5-30",
label: "5:30 PM – 5:45 PM",
value: "5-30",
},
{
description: "Most popular · High demand",
id: "delivery-6-00",
label: "6:00 PM – 6:15 PM",
value: "6-00",
},
{
description: "Last slot before kitchen closes",
id: "delivery-6-30",
label: "6:30 PM – 6:45 PM",
value: "6-30",
},
];
const DrawerDemo = () => {
const [open, setOpen] = useState(false);
const [deliveryTime, setDeliveryTime] = useState("asap");
const isMobile = useIsMobile();
const handleOpenChange = (details: { open: boolean }) => {
setOpen(details.open);
};
const handleDeliveryTimeChange = (details: { value: string | null }) => {
if (details.value !== null) {
setDeliveryTime(details.value);
}
};
const handleConfirm = () => {
const selected = deliveryTimes.find((time) => time.value === deliveryTime);
if (!selected) {
return;
}
setOpen(false);
toast.create({
description: selected.label,
title: "Delivery time confirmed",
});
};
return (
<Drawer
onOpenChange={handleOpenChange}
open={open}
swipeDirection={isMobile ? "down" : "right"}
>
<DrawerTrigger asChild>
<Button variant="secondary">Open Drawer</Button>
</DrawerTrigger>
<DrawerContent showBar={isMobile}>
<DrawerHeader>
<DrawerTitle>Pick a delivery time</DrawerTitle>
<DrawerDescription>
We'll prepare your order as soon as possible.
</DrawerDescription>
</DrawerHeader>
<div className="scroll-fade flex-1 overflow-y-auto p-4">
<RadioGroup
className="gap-2"
onValueChange={handleDeliveryTimeChange}
value={deliveryTime}
>
{deliveryTimes.map((time) => (
<FieldLabel key={time.value}>
<Field orientation="horizontal">
<FieldContent>
<FieldTitle className="flex items-center gap-2">
{time.label}
{time.badge ? (
<Badge variant="secondary">{time.badge}</Badge>
) : null}
</FieldTitle>
<FieldDescription>{time.description}</FieldDescription>
</FieldContent>
<RadioGroupItem value={time.value}>
<span className="sr-only">{time.label}</span>
</RadioGroupItem>
</Field>
</FieldLabel>
))}
</RadioGroup>
</div>
<DrawerFooter>
<Button className="h-[34px]" onClick={handleConfirm}>
Confirm Delivery Time
</Button>
<DrawerClose asChild>
<Button variant="outline">Cancel</Button>
</DrawerClose>
</DrawerFooter>
</DrawerContent>
</Drawer>
);
};
Accessibility
| Key | Description |
|---|---|
Enter | Activate the focused control. |
Space | Activate the focused control. |
End | Go to the end. |
Demos
Sides
Position the drawer with swipeDirection.
Swipe Handle
Show a swipe handle with showBar on DrawerContent.
Nested
Open drawers from inside another drawer.
Non Modal
Allow page interaction while the drawer is open with modal={false}.
Snap Points
Snap a vertical drawer to preset heights with snapPoints.
Dialog
Combine Dialog and Drawer for a responsive edit form.
API Reference
Drawer
PropType
AttributeType
DrawerContent
div
AttributeType
CSS variableType
--bleedstring--closed-transformstring--color-popoverstring--drawer-content-heightstring--drawer-swipe-movement-xstring--drawer-swipe-movement-ystring--drawer-swipe-progressstring--drawer-swipe-strengthstring--nested-drawersstring--peekstring--stack-heightstring--stack-peek-offsetstring--stack-progressstring--stack-scalestring--stack-scale-basestring--stack-shrinkstring--stack-stepstringDrawerTitle
h2
PropType
AttributeType
DrawerDescription
p
PropType
AttributeType
DrawerTrigger
button
PropType
AttributeType
DrawerOverlay
div
PropType
AttributeType
CSS variableType
--drawer-swipe-progressstring--drawer-swipe-strengthstringDrawerPortal
div
PropType
AttributeType
DrawerClose
button
PropType
AttributeType
DrawerSwipeHandle
PropType
AttributeType
DrawerHeader
PropType
AttributeType
DrawerFooter
PropType
AttributeType