Drawer
A drawer component for React.
tsx
"use client"
import * as React from "react"import { Minus, Plus } from "lucide-react"import { Bar, BarChart, ResponsiveContainer } from "recharts"
import { Button } from "@/components/ui/button"import { Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerTitle, DrawerTrigger,} from "@/components/ui/drawer"
const data = [ { goal: 400 }, { goal: 300 }, { goal: 200 }, { goal: 300 }, { goal: 200 }, { goal: 278 }, { goal: 189 }, { goal: 239 }, { goal: 300 }, { goal: 200 }, { goal: 278 }, { goal: 189 }, { goal: 349 },]
export function DrawerDemo() { const [goal, setGoal] = React.useState(350)
function onClick(adjustment: number) { setGoal(Math.max(200, Math.min(400, goal + adjustment))) }
return ( <Drawer> <DrawerTrigger asChild> <Button variant="outline">Open Drawer</Button> </DrawerTrigger> <DrawerContent> <div className="mx-auto w-full max-w-sm"> <DrawerHeader> <DrawerTitle>Move Goal</DrawerTitle> <DrawerDescription>Set your daily activity goal.</DrawerDescription> </DrawerHeader> <div className="p-4 pb-0"> <div className="flex items-center justify-center space-x-2"> <Button variant="outline" size="icon" className="h-8 w-8 shrink-0 rounded-full" onClick={() => onClick(-10)} disabled={goal <= 200} > <Minus /> <span className="sr-only">Decrease</span> </Button> <div className="flex-1 text-center"> <div className="text-7xl font-bold tracking-tighter"> {goal} </div> <div className="text-muted-foreground text-[0.70rem] uppercase"> Calories/day </div> </div> <Button variant="outline" size="icon" className="h-8 w-8 shrink-0 rounded-full" onClick={() => onClick(10)} disabled={goal >= 400} > <Plus /> <span className="sr-only">Increase</span> </Button> </div> <div className="mt-3 h-[120px]"> <ResponsiveContainer width="100%" height="100%"> <BarChart data={data}> <Bar dataKey="goal" style={ { fill: "hsl(var(--foreground))", opacity: 0.9, } as React.CSSProperties } /> </BarChart> </ResponsiveContainer> </div> </div> <DrawerFooter> <Button>Submit</Button> <DrawerClose asChild> <Button variant="outline">Cancel</Button> </DrawerClose> </DrawerFooter> </div> </DrawerContent> </Drawer> )}
Installation
CLI
bash
npx fivui add drawer
Manual
Install the following dependencies:
bash
npm install vaul
Copy and paste the following code into your project:
tsx
"use client"
import * as React from "react"import { Drawer as DrawerPrimitive } from "vaul"
import { cn } from "@/lib/utils"
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 DrawerPortal({ ...props}: React.ComponentProps<typeof DrawerPrimitive.Portal>) { return <DrawerPrimitive.Portal data-slot="drawer-portal" {...props} />}
function DrawerClose({ ...props}: React.ComponentProps<typeof DrawerPrimitive.Close>) { return <DrawerPrimitive.Close data-slot="drawer-close" {...props} />}
function DrawerOverlay({ className, ...props}: React.ComponentProps<typeof DrawerPrimitive.Overlay>) { return ( <DrawerPrimitive.Overlay data-slot="drawer-overlay" className={cn( "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50", className )} {...props} /> )}
function DrawerContent({ className, children, ...props}: React.ComponentProps<typeof DrawerPrimitive.Content>) { return ( <DrawerPortal data-slot="drawer-portal"> <DrawerOverlay /> <DrawerPrimitive.Content data-slot="drawer-content" className={cn( "group/drawer-content bg-background fixed z-50 flex h-auto flex-col", "data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=top]:rounded-b-lg data-[vaul-drawer-direction=top]:border-b", "data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=bottom]:rounded-t-lg data-[vaul-drawer-direction=bottom]:border-t", "data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=right]:border-l data-[vaul-drawer-direction=right]:sm:max-w-sm", "data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=left]:border-r data-[vaul-drawer-direction=left]:sm:max-w-sm", className )} {...props} > <div className="bg-muted mx-auto mt-4 hidden h-2 w-[100px] shrink-0 rounded-full group-data-[vaul-drawer-direction=bottom]/drawer-content:block" /> {children} </DrawerPrimitive.Content> </DrawerPortal> )}
function DrawerHeader({ className, ...props }: React.ComponentProps<"div">) { return ( <div data-slot="drawer-header" className={cn( "flex flex-col gap-0.5 p-4 group-data-[vaul-drawer-direction=bottom]/drawer-content:text-center group-data-[vaul-drawer-direction=top]/drawer-content:text-center md:gap-1.5 md:text-left", className )} {...props} /> )}
function DrawerFooter({ className, ...props }: React.ComponentProps<"div">) { return ( <div data-slot="drawer-footer" className={cn("mt-auto flex flex-col gap-2 p-4", className)} {...props} /> )}
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, DrawerPortal, DrawerOverlay, DrawerTrigger, DrawerClose, DrawerContent, DrawerHeader, DrawerFooter, DrawerTitle, DrawerDescription,}
Usage
tsx
import { Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerTitle, DrawerTrigger,} from "@/components/ui/drawer"
tsx
<Drawer> <DrawerTrigger>Open</DrawerTrigger> <DrawerContent> <DrawerHeader> <DrawerTitle>Are you absolutely sure?</DrawerTitle> <DrawerDescription>This action cannot be undone.</DrawerDescription> </DrawerHeader> <DrawerFooter> <Button>Submit</Button> <DrawerClose> <Button variant="outline">Cancel</Button> </DrawerClose> </DrawerFooter> </DrawerContent></Drawer>
Examples
Simple Drawer
Simple Drawer
A basic drawer with header and footer
tsx
export function DrawerSimpleDemo() { return ( <Drawer> <DrawerTrigger asChild> <Button variant="outline">Open Simple Drawer</Button> </DrawerTrigger> <DrawerContent> <DrawerHeader> <DrawerTitle>Are you absolutely sure?</DrawerTitle> <DrawerDescription>This action cannot be undone.</DrawerDescription> </DrawerHeader> <DrawerFooter> <Button>Submit</Button> <DrawerClose asChild> <Button variant="outline">Cancel</Button> </DrawerClose> </DrawerFooter> </DrawerContent> </Drawer> )}
Form Drawer
Form Drawer
A drawer containing a form with input fields
tsx
import { Input } from "@/components/ui/input"import { Label } from "@/components/ui/label"
export function DrawerFormDemo() { return ( <Drawer> <DrawerTrigger asChild> <Button variant="outline">Edit Profile</Button> </DrawerTrigger> <DrawerContent> <div className="mx-auto w-full max-w-sm"> <DrawerHeader> <DrawerTitle>Edit Profile</DrawerTitle> <DrawerDescription> Make changes to your profile here. Click save when you're done. </DrawerDescription> </DrawerHeader> <div className="p-4 pb-0"> <div className="grid gap-4"> <div className="grid gap-2"> <Label htmlFor="name">Name</Label> <Input id="name" placeholder="Enter your name" /> </div> <div className="grid gap-2"> <Label htmlFor="email">Email</Label> <Input id="email" placeholder="Enter your email" /> </div> </div> </div> <DrawerFooter> <Button>Save changes</Button> <DrawerClose asChild> <Button variant="outline">Cancel</Button> </DrawerClose> </DrawerFooter> </div> </DrawerContent> </Drawer> )}
Responsive Dialog
You can combine the Dialog and Drawer components to create a responsive dialog. This renders a Dialog component on desktop and a Drawer on mobile.
Responsive Dialog
Dialog on desktop, drawer on mobile
tsx
"use client"
import * as React from "react"import { cn } from "@/lib/utils"import { useMediaQuery } from "@/hooks/use-media-query"import { Button } from "@/components/ui/button"import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger,} from "@/components/ui/dialog"import { Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerTitle, DrawerTrigger,} from "@/components/ui/drawer"import { Input } from "@/components/ui/input"import { Label } from "@/components/ui/label"
export function DrawerDialogDemo() { const [open, setOpen] = React.useState(false) const isDesktop = useMediaQuery("(min-width: 768px)")
if (isDesktop) { return ( <Dialog open={open} onOpenChange={setOpen}> <DialogTrigger asChild> <Button variant="outline">Edit Profile</Button> </DialogTrigger> <DialogContent className="sm:max-w-[425px]"> <DialogHeader> <DialogTitle>Edit profile</DialogTitle> <DialogDescription> Make changes to your profile here. Click save when you're done. </DialogDescription> </DialogHeader> <ProfileForm /> </DialogContent> </Dialog> ) }
return ( <Drawer open={open} onOpenChange={setOpen}> <DrawerTrigger asChild> <Button variant="outline">Edit Profile</Button> </DrawerTrigger> <DrawerContent> <DrawerHeader className="text-left"> <DrawerTitle>Edit profile</DrawerTitle> <DrawerDescription> Make changes to your profile here. Click save when you're done. </DrawerDescription> </DrawerHeader> <ProfileForm className="px-4" /> <DrawerFooter className="pt-2"> <DrawerClose asChild> <Button variant="outline">Cancel</Button> </DrawerClose> </DrawerFooter> </DrawerContent> </Drawer> )}
function ProfileForm({ className }: React.ComponentProps<"form">) { return ( <form className={cn("grid items-start gap-6", className)}> <div className="grid gap-3"> <Label htmlFor="email">Email</Label> <Input type="email" id="email" defaultValue="user@example.com" /> </div> <div className="grid gap-3"> <Label htmlFor="username">Username</Label> <Input id="username" defaultValue="@username" /> </div> <Button type="submit">Save changes</Button> </form> )}
API Reference
Drawer
Prop | Type | Default | Description |
---|---|---|---|
open | boolean | - | The controlled open state of the drawer. |
onOpenChange | function | - | Event handler called when the open state changes. |
direction | "top" | "bottom" | "left" | "right" | "bottom" | The direction from which the drawer slides in. |
DrawerTrigger
Prop | Type | Default | Description |
---|---|---|---|
asChild | boolean | false | Change the component to the HTML tag or custom component of the only child. |
DrawerContent
Prop | Type | Default | Description |
---|---|---|---|
className | string | - | Additional CSS classes to apply to the drawer content. |
External Documentation
Drawer is built on top of Vaul by Emil Kowalski. For more advanced usage and API details, refer to the official documentation.
View Vaul Documentation