Sections
Components
- Accordion
- Alert
- Alert Dialog
- Aspect Ratio
- Attachment
- Avatar
- Badge
- Breadcrumb
- Bubble
- Button
- Button Group
- Calendar
- Card
- Carousel
- Chart
- Checkbox
- Collapsible
- Combobox
- Command
- Context Menu
- Data Table
- Date Picker
- Dialog
- Drawer
- Dropdown Menu
- Empty
- Field
- Form
- Hover Card
- Input
- Input Group
- Input OTP
- Item
- Kbd
- Label
- Marker
- Menubar
- Message
- Message Scroller
- Native Select
- Navigation Menu
- Number Field
- Pagination
- Pin Input
- Popover
- Progress
- Questionnaire
- Radio Group
- Range Calendar
- Resizable
- Scroll Area
- Select
- Separator
- Sheet
- Sidebar
- Skeleton
- Slider
- Sonner
- Spinner
- Stepper
- Switch
- Table
- Tabs
- Tags Input
- Textarea
- Toast
- Toggle
- Toggle Group
- Tooltip
- Typography
Directory
Forms
Utilities
<script setup lang="ts">
import { Minus, Plus } from '@lucide/vue'
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 },
]
const goal = ref(350)
</script>
<template>
<Drawer>
<DrawerTrigger as-child>
<Button variant="outline">
Open Drawer
</Button>
</DrawerTrigger>
<DrawerContent>
<div class="mx-auto w-full max-w-sm">
<DrawerHeader>
<DrawerTitle>Move Goal</DrawerTitle>
<DrawerDescription>Set your daily activity goal.</DrawerDescription>
</DrawerHeader>
<div class="p-4 pb-0">
<div class="flex items-center justify-center space-x-2">
<Button
variant="outline"
size="icon"
class="h-8 w-8 shrink-0 rounded-full"
:disabled="goal <= 200"
@click="goal -= 10"
>
<Minus />
<span class="sr-only">Decrease</span>
</Button>
<div class="flex-1 text-center">
<div class="text-7xl font-bold tracking-tighter">
{{ goal }}
</div>
<div class="text-muted-foreground text-[0.70rem] uppercase">
Calories/day
</div>
</div>
<Button
variant="outline"
size="icon"
class="h-8 w-8 shrink-0 rounded-full"
:disabled="goal >= 400"
@click="goal += 10"
>
<Plus />
<span class="sr-only">Increase</span>
</Button>
</div>
<div class="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 as-child>
<Button variant="outline">
Cancel
</Button>
</DrawerClose>
</DrawerFooter>
</div>
</DrawerContent>
</Drawer>
</template>Installation
pnpm dlx shadcn-vue@latest add drawer
Usage
<script setup lang="ts">
import {
Drawer,
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
} from '@/components/ui/drawer'
</script>
<template>
<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>
</template>Example
Scrollable Content
Keep actions visible while the content scrolls.
<script setup lang="ts">
import { Button } from '@/components/ui/button'
import {
Drawer,
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
} from '@/components/ui/drawer'
</script>
<template>
<Drawer swipe-direction="right">
<DrawerTrigger as-child>
<Button variant="outline">
Scrollable Content
</Button>
</DrawerTrigger>
<DrawerContent>
<DrawerHeader>
<DrawerTitle>Move Goal</DrawerTitle>
<DrawerDescription>Set your daily activity goal.</DrawerDescription>
</DrawerHeader>
<div class="no-scrollbar overflow-y-auto px-4">
<p v-for="value in 10" :key="value" class="mb-4 leading-normal style-lyra:mb-2 style-lyra:leading-relaxed">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
enim ad minim veniam, quis nostrud exercitation ullamco laboris
nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
reprehenderit in voluptate velit esse cillum dolore eu fugiat
nulla pariatur. Excepteur sint occaecat cupidatat non proident,
sunt in culpa qui officia deserunt mollit anim id est laborum.
</p>
</div>
<DrawerFooter>
<Button>Submit</Button>
<DrawerClose as-child>
<Button variant="outline">
Cancel
</Button>
</DrawerClose>
</DrawerFooter>
</DrawerContent>
</Drawer>
</template>Sides
Use the swipe-direction prop to set the direction the drawer swipes toward to dismiss. The drawer is anchored to the edge it dismisses toward, so up opens a drawer along the top edge and down opens one along the bottom edge. Available options are up, right, down, and left.
<script setup lang="ts">
import { Button } from '@/components/ui/button'
import {
Drawer,
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
} from '@/components/ui/drawer'
const DRAWER_SIDES = ['top', 'right', 'bottom', 'left'] as const
// The drawer sits at the edge it dismisses toward: a drawer on the
// `top` edge swipes `up` to close, one on the `bottom` edge swipes `down`.
const SIDE_TO_SWIPE_DIRECTION = {
top: 'up',
right: 'right',
bottom: 'down',
left: 'left',
} as const
</script>
<template>
<div class="flex flex-wrap gap-2">
<Drawer v-for="side in DRAWER_SIDES" :key="side" :swipe-direction="SIDE_TO_SWIPE_DIRECTION[side]">
<DrawerTrigger as-child>
<Button variant="outline" class="capitalize">
{{ side }}
</Button>
</DrawerTrigger>
<DrawerContent class="data-[swipe-direction=up]:max-h-[50vh] data-[swipe-direction=down]:max-h-[50vh]">
<DrawerHeader>
<DrawerTitle>Move Goal</DrawerTitle>
<DrawerDescription>
Set your daily activity goal.
</DrawerDescription>
</DrawerHeader>
<div class="no-scrollbar overflow-y-auto px-4">
<p v-for="value in 10" :key="value" class="mb-4 leading-normal style-lyra:mb-2 style-lyra:leading-relaxed">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
enim ad minim veniam, quis nostrud exercitation ullamco laboris
nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
reprehenderit in voluptate velit esse cillum dolore eu fugiat
nulla pariatur. Excepteur sint occaecat cupidatat non proident,
sunt in culpa qui officia deserunt mollit anim id est laborum.
</p>
</div>
<DrawerFooter>
<Button>Submit</Button>
<DrawerClose as-child>
<Button variant="outline">
Cancel
</Button>
</DrawerClose>
</DrawerFooter>
</DrawerContent>
</Drawer>
</div>
</template>Responsive Modal (Dialog & Drawer)
Use a Drawer component for smaller viewport sizes and a Dialog component otherwise. This can be further made reusable by using slots for various parts of the modal.
<script setup lang="ts">
import { useMediaQuery } from '@vueuse/core'
import { Button } from '@/components/ui/button'
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
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'
const isDesktop = useMediaQuery('(min-width: 640px)')
const Modal = computed(() => ({
Root: isDesktop.value ? Dialog : Drawer,
Trigger: isDesktop.value ? DialogTrigger : DrawerTrigger,
Content: isDesktop.value ? DialogContent : DrawerContent,
Header: isDesktop.value ? DialogHeader : DrawerHeader,
Title: isDesktop.value ? DialogTitle : DrawerTitle,
Description: isDesktop.value ? DialogDescription : DrawerDescription,
Footer: isDesktop.value ? DialogFooter : DrawerFooter,
Close: isDesktop.value ? DialogClose : DrawerClose,
}))
const open = ref(false)
</script>
<template>
<component :is="Modal.Root" v-model:open="open">
<component :is="Modal.Trigger" as-child>
<Button variant="outline">
Open Dialog
</Button>
</component>
<component
:is="Modal.Content"
class="sm:max-w-md" :class="[
{ 'px-2 pb-8 *:px-4': !isDesktop },
]"
>
<component :is="Modal.Header">
<component :is="Modal.Title">
Share Link
</component>
<component :is="Modal.Description">
Anyone who has this link will be able to view this.
</component>
</component>
<div class="flex items-center gap-2">
<div class="grid flex-1 gap-2">
<Label for="link" class="sr-only">
Link
</Label>
<Input
id="link"
default-value="https://www.shadcn-vue.com/docs/installation"
read-only
/>
</div>
</div>
<component :is="Modal.Footer" class="pt-4">
<component :is="Modal.Close" as-child>
<Button variant="outline">
Close
</Button>
</component>
</component>
</component>
</component>
</template>