A React component for creating drawers.
vaul1'use client'
2
3import * as React from 'react'
4import { CalendarDays, MapPin } from 'lucide-react'
5
6import { Button } from '@/components/ui/button'
7import {
8 Drawer,
9 DrawerClose,
10 DrawerContent,
11 DrawerDescription,
12 DrawerFooter,
13 DrawerHeader,
14 DrawerTitle,
15 DrawerTrigger,
16} from '@/components/ui/drawer'
17
18export function DrawerDemo() {
19 return (
20 <Drawer>
21 <DrawerTrigger asChild>
22 <Button variant="outline">View Event</Button>
23 </DrawerTrigger>
24 <DrawerContent>
25 <div className="mx-auto w-full max-w-sm">
26 <DrawerHeader>
27 <DrawerTitle>Annual Tech Meetup</DrawerTitle>
28 <DrawerDescription>
29 Connect with developers, designers, and creators.
30 </DrawerDescription>
31 </DrawerHeader>
32 <div className="space-y-2 p-4 text-sm">
33 <div className="flex items-center gap-2">
34 <CalendarDays className="text-primary size-4" />
35 <span>October 25, 2025 • 10:00 AM</span>
36 </div>
37 <div className="flex items-center gap-2">
38 <MapPin className="text-primary size-4" />
39 <span>Innovation Hub, San Francisco</span>
40 </div>
41 <p className="text-muted-foreground mt-3">
42 Join us for a full-day event exploring the latest in
43 web development, AI, and design systems.
44 </p>
45 </div>
46 <DrawerFooter>
47 <Button>Register</Button>
48 <DrawerClose asChild>
49 <Button variant="outline">Cancel</Button>
50 </DrawerClose>
51 </DrawerFooter>
52 </div>
53 </DrawerContent>
54 </Drawer>
55 )
56}
57Install the following dependencies:
Combine the Dialog and Drawer components to achieve a responsive layout: Dialog for desktop, Drawer for mobile.
1'use client'
2
3import * as React from 'react'
4
5import { cn } from '@/lib/utils'
6import { Button } from '@/components/ui/button'
7import {
8 Dialog,
9 DialogContent,
10 DialogDescription,
11 DialogHeader,
12 DialogTitle,
13 DialogTrigger,
14} from '@/components/ui/dialog'
15import {
16 Drawer,
17 DrawerClose,
18 DrawerContent,
19 DrawerDescription,
20 DrawerFooter,
21 DrawerHeader,
22 DrawerTitle,
23 DrawerTrigger,
24} from '@/components/ui/drawer'
25import { Input } from '@/components/ui/input'
26import { Label } from '@/components/ui/label'
27
28export function DrawerDialogDemo() {
29 const [open, setOpen] = React.useState(false)
30 const isDesktop = useMediaQuery('(min-width: 768px)')
31
32 if (isDesktop) {
33 return (
34 <Dialog open={open} onOpenChange={setOpen}>
35 <DialogTrigger asChild>
36 <Button variant="outline">Log In</Button>
37 </DialogTrigger>
38 <DialogContent className="sm:max-w-[425px]">
39 <DialogHeader>
40 <DialogTitle>Log In</DialogTitle>
41 <DialogDescription>
42 Enter your details Here. Click save when you're
43 done.
44 </DialogDescription>
45 </DialogHeader>
46 <LogInForm />
47 </DialogContent>
48 </Dialog>
49 )
50 }
51
52 return (
53 <Drawer open={open} onOpenChange={setOpen}>
54 <DrawerTrigger asChild>
55 <Button variant="outline">Log In</Button>
56 </DrawerTrigger>
57 <DrawerContent>
58 <DrawerHeader className="text-left">
59 <DrawerTitle>Log In</DrawerTitle>
60 <DrawerDescription>
61 Enter your details Here. Click save when you're
62 done.
63 </DrawerDescription>
64 </DrawerHeader>
65 <LogInForm className="px-4" />
66 <DrawerFooter className="pt-2">
67 <DrawerClose asChild>
68 <Button variant="outline">Cancel</Button>
69 </DrawerClose>
70 </DrawerFooter>
71 </DrawerContent>
72 </Drawer>
73 )
74}
75
76function LogInForm({ className }: React.ComponentProps<'form'>) {
77 return (
78 <form className={cn('grid items-start gap-6', className)}>
79 <div className="grid gap-3">
80 <Label htmlFor="email">Email</Label>
81 <Input
82 type="email"
83 id="email"
84 defaultValue="rafael.costa@example.com"
85 />
86 </div>
87 <div className="grid gap-3">
88 <Label htmlFor="password">Password</Label>
89 <Input
90 id="password"
91 type="password"
92 defaultValue="rafaelCost@123"
93 />
94 </div>
95 <Button type="submit">Save changes</Button>
96 </form>
97 )
98}
99
100function useMediaQuery(query: string) {
101 const [matches, setMatches] = React.useState(false)
102
103 React.useEffect(() => {
104 const media = window.matchMedia(query)
105 const listener = () => setMatches(media.matches)
106 setMatches(media.matches)
107
108 media.addEventListener('change', listener)
109 return () => media.removeEventListener('change', listener)
110 }, [query])
111
112 return matches
113}
114Root component that controls the open/close state of the drawer.
| Prop | Type | Default |
|---|---|---|
| direction | top,bottom,left,right | - |
| open | boolean | - |
| onOpenChange | (open: boolean) => void | - |
Element used to toggle the drawer’s visibility.
| Prop | Type | Default |
|---|---|---|
| asChild | boolean | - |
The main panel that slides in from the chosen direction.
| Prop | Type | Default |
|---|---|---|
| children | ReactNode | - |
The main panel that slides in from the chosen direction.
| Prop | Type | Default |
|---|---|---|
| asChild | boolean | - |