Drawer
A panel that slides in from the edge of the screen.
Installation
$
Usage
import { Drawer, DrawerTrigger, DrawerContent, DrawerHeader, DrawerFooter, DrawerTitle, DrawerDescription } from "@/components/ui/drawer"Examples
Default
API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
| sideDrawerContent | "top" | "right" | "bottom" | "left" | "right" | Edge of the screen the drawer slides in from. |
| sizeDrawerContent | "sm" | "md" | "lg" | "xl" | "2xl" | "sm" | Maximum width of the panel for left/right drawers; top/bottom drawers span the full width. |
| showCloseButtonDrawerContent | boolean | true | Shows the X close button in the top corner. |
| showCancelButtonDrawerFooter | boolean | false | Appends a ghost Cancel button that closes the drawer. |
"use client"
import * as React from "react"
import { Dialog as DrawerPrimitive } from "@base-ui/react/dialog"
import { XIcon } from "lucide-react"
import { cn } from "@/registry/lib/utils"
import { Button } from "@/registry/ui/button"
import { blurredOverlayClass } from "@/registry/lib/tokens"
function Drawer({ ...props }: React.ComponentProps<typeof DrawerPrimitive.Root>) {
return <DrawerPrimitive.Root data-slot="drawer" {...props} />
}
function DrawerTrigger({
...props
}: React.ComponentProps<typeof DrawerPrimitive.Trigger>) {
return <DrawerPrimitive.Trigger data-slot="drawer-trigger" {...props} />
}
function DrawerClose({
...props
}: React.ComponentProps<typeof DrawerPrimitive.Close>) {
return <DrawerPrimitive.Close data-slot="drawer-close" {...props} />
}
function DrawerPortal({
...props
}: React.ComponentProps<typeof DrawerPrimitive.Portal>) {
return <DrawerPrimitive.Portal data-slot="drawer-portal" {...props} />
}
function DrawerOverlay({
className,
...props
}: React.ComponentProps<typeof DrawerPrimitive.Backdrop>) {
return (
<DrawerPrimitive.Backdrop
data-slot="drawer-overlay"
className={cn(
blurredOverlayClass,
className
)}
{...props}
/>
)
}
const drawerSizeMap = {
sm: "sm:max-w-sm",
md: "sm:max-w-md",
lg: "sm:max-w-lg",
xl: "sm:max-w-xl",
"2xl": "sm:max-w-2xl",
} as const
type DrawerSize = keyof typeof drawerSizeMap
function DrawerContent({
className,
children,
side = "right",
size = "sm",
showCloseButton = true,
...props
}: React.ComponentProps<typeof DrawerPrimitive.Popup> & {
side?: "top" | "right" | "bottom" | "left"
size?: DrawerSize
showCloseButton?: boolean
}) {
const sizeClass = drawerSizeMap[size]
return (
<DrawerPortal>
<DrawerOverlay />
<DrawerPrimitive.Popup
data-slot="drawer-content"
className={cn(
"bg-background transition-[translate] motion-reduce:transition-none ease-out duration-200 data-[ending-style]:duration-150 fixed z-50 flex flex-col gap-4 px-4 pb-4 pt-10 shadow-ring-md outline-none",
side === "right" &&
`data-[starting-style]:translate-x-full data-[ending-style]:translate-x-full top-3 bottom-3 right-3 w-3/4 rounded-xl ${sizeClass}`,
side === "left" &&
`data-[starting-style]:-translate-x-full data-[ending-style]:-translate-x-full top-3 bottom-3 left-3 w-3/4 rounded-xl ${sizeClass}`,
side === "top" &&
"data-[starting-style]:-translate-y-full data-[ending-style]:-translate-y-full inset-x-3 top-3 h-auto rounded-xl",
side === "bottom" &&
"data-[starting-style]:translate-y-full data-[ending-style]:translate-y-full inset-x-3 bottom-3 h-auto rounded-xl",
className
)}
{...props}
>
{children}
{showCloseButton && (
<DrawerPrimitive.Close
data-slot="drawer-close"
className="tap-target ring-offset-background focus-visible:ring-ring-focus absolute top-3 right-3 rounded-xs opacity-70 transition-opacity ease-out hover:opacity-100 focus-visible:ring-[3px] focus-visible:ring-offset-2 outline-none disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
>
<XIcon />
<span className="sr-only">Close</span>
</DrawerPrimitive.Close>
)}
</DrawerPrimitive.Popup>
</DrawerPortal>
)
}
function DrawerHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="drawer-header"
className={cn("flex flex-col gap-2 pr-8", className)}
{...props}
/>
)
}
function DrawerFooter({
className,
showCancelButton = false,
children,
...props
}: React.ComponentProps<"div"> & {
showCancelButton?: boolean
}) {
return (
<div
data-slot="drawer-footer"
className={cn("mt-auto flex flex-col gap-2", className)}
{...props}
>
{children}
{showCancelButton && (
<DrawerPrimitive.Close render={<Button variant="ghost">Cancel</Button>} />
)}
</div>
)
}
function DrawerTitle({
className,
...props
}: React.ComponentProps<typeof DrawerPrimitive.Title>) {
return (
<DrawerPrimitive.Title
data-slot="drawer-title"
className={cn("text-foreground font-semibold", className)}
{...props}
/>
)
}
function DrawerDescription({
className,
...props
}: React.ComponentProps<typeof DrawerPrimitive.Description>) {
return (
<DrawerPrimitive.Description
data-slot="drawer-description"
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
)
}
export {
Drawer,
DrawerTrigger,
DrawerClose,
DrawerContent,
DrawerHeader,
DrawerFooter,
DrawerTitle,
DrawerDescription,
}