Sheet
sheetuiSide-mounted modal that slides in from the top, right, bottom, or left edge. Use for filter panels, edit drawers, and mobile menus.
When to use which
Decision GuideDialogs capture immediate user focus for critical actions. Sheets slide in from screen edges for deep secondary forms, while Popovers provide lightweight contextual cards.
| Component | Role | Use when |
|---|---|---|
| Dialog Critical confirmations, destructive warnings, or focused single-purpose tasks requiring user resolution. | Modal Window | |
| Sheetcurrent Complex edit forms, filter panes, or shopping carts that retain underlying page context. | Off-Canvas Slide-Over | |
| Drawer Mobile bottom sheets, touch-friendly sliding panels, or secondary inspection trays. | Edge Panel / Bottom Sheet | |
| Popover Small contextual dropdowns, color pickers, date pickers, or inline configuration flyouts. | Contextual Floating Card |
Installation
$pnpm dlx shadcn-vue@latest add https://uipkge.dev/r/vue/sheet.json$npx shadcn-vue@latest add https://uipkge.dev/r/vue/sheet.json$yarn dlx shadcn-vue@latest add https://uipkge.dev/r/vue/sheet.json$bunx shadcn-vue@latest add https://uipkge.dev/r/vue/sheet.jsonnpx shadcn-vue@latest add @uipkge/sheetInstalls to:app/components/ui/sheet/Examples
npm dependencies
Used by
Files installed (10)
app/components/ui/sheet/Sheet.vue0.4 kB
<script setup lang="ts"> import type { DialogRootEmits, DialogRootProps } from 'reka-ui' import { DialogRoot, useForwardPropsEmits } from 'reka-ui' const props = defineProps<DialogRootProps>() const emits = defineEmits<DialogRootEmits>() const forwarded = useForwardPropsEmits(props, emits) </script> <template> <DialogRoot v-slot="slotProps" data-uipkge data-slot="sheet" v-bind="forwarded"> <slot v-bind="slotProps" /> </DialogRoot> </template>app/components/ui/sheet/SheetClose.vue0.3 kB
<script setup lang="ts"> import type { DialogCloseProps } from 'reka-ui' import { DialogClose } from 'reka-ui' const props = defineProps<DialogCloseProps>() </script> <template> <DialogClose data-uipkge data-slot="sheet-close" v-bind="props"> <slot /> </DialogClose> </template>app/components/ui/sheet/SheetContent.vue3.1 kB
<script setup lang="ts"> import type { DialogContentEmits, DialogContentProps } from 'reka-ui' import type { HTMLAttributes } from 'vue' import { reactiveOmit } from '@vueuse/core' import { X } from 'lucide-vue-next' import { DialogClose, DialogContent, DialogPortal, useForwardPropsEmits } from 'reka-ui' import { cn } from '@/lib/utils' import SheetOverlay from './SheetOverlay.vue' // SFC compiler quirk: when the extension type is a *named* interface // in an intersection (`DialogContentProps & ExtraProps`), the runtime // extractor occasionally drops fields from the named interface -- // `side` then warns "accessed during render but not defined on // instance" every time the FilterSheet mounts. Inlining the literal // keeps the extractor happy. DialogContentProps stays in the // intersection so reka-ui's typed props (asChild, trapFocus, etc.) // flow through useForwardPropsEmits below. defineOptions({ inheritAttrs: false, }) const props = withDefaults( defineProps< DialogContentProps & { class?: HTMLAttributes['class'] side?: 'top' | 'right' | 'bottom' | 'left' } >(), { side: 'right' }, ) const emits = defineEmits<DialogContentEmits>() const delegatedProps = reactiveOmit(props, 'class', 'side') const forwarded = useForwardPropsEmits(delegatedProps, emits) </script> <template> <DialogPortal> <SheetOverlay /> <DialogContent data-uipkge data-slot="sheet-content" :class=" cn( 'bg-background motion-safe:data-[state=open]:animate-in motion-safe:data-[state=closed]:animate-out motion-safe:data-[state=open]:ease-emphasized motion-safe:data-[state=open]:blur-in-2 motion-safe:data-[state=closed]:blur-out-2 fixed z-50 flex flex-col gap-4 overflow-hidden shadow-lg transition ease-in-out motion-safe:data-[state=closed]:duration-200 motion-safe:data-[state=open]:duration-300', side === 'right' && 'motion-safe:data-[state=closed]:slide-out-to-right motion-safe:data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm', side === 'left' && 'motion-safe:data-[state=closed]:slide-out-to-left motion-safe:data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm', side === 'top' && 'motion-safe:data-[state=closed]:slide-out-to-top motion-safe:data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b', side === 'bottom' && 'motion-safe:data-[state=closed]:slide-out-to-bottom motion-safe:data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t', props.class, ) " v-bind="{ ...$attrs, ...forwarded }" > <slot /> <DialogClose class="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none" > <X class="size-4" aria-hidden="true" /> <span class="sr-only">Close</span> </DialogClose> </DialogContent> </DialogPortal> </template>app/components/ui/sheet/SheetDescription.vue0.6 kB
<script setup lang="ts"> import type { DialogDescriptionProps } from 'reka-ui' import type { HTMLAttributes } from 'vue' import { reactiveOmit } from '@vueuse/core' import { DialogDescription } from 'reka-ui' import { cn } from '@/lib/utils' const props = defineProps<DialogDescriptionProps & { class?: HTMLAttributes['class'] }>() const delegatedProps = reactiveOmit(props, 'class') </script> <template> <DialogDescription data-uipkge data-slot="sheet-description" :class="cn('text-muted-foreground text-sm', props.class)" v-bind="delegatedProps" > <slot /> </DialogDescription> </template>app/components/ui/sheet/SheetFooter.vue0.3 kB
<script setup lang="ts"> import type { HTMLAttributes } from 'vue' import { cn } from '@/lib/utils' const props = defineProps<{ class?: HTMLAttributes['class'] }>() </script> <template> <div data-uipkge data-slot="sheet-footer" :class="cn('mt-auto flex flex-col gap-2 p-4', props.class)"> <slot /> </div> </template>app/components/ui/sheet/SheetHeader.vue0.3 kB
<script setup lang="ts"> import type { HTMLAttributes } from 'vue' import { cn } from '@/lib/utils' const props = defineProps<{ class?: HTMLAttributes['class'] }>() </script> <template> <div data-uipkge data-slot="sheet-header" :class="cn('flex flex-col gap-1.5 p-4', props.class)"> <slot /> </div> </template>app/components/ui/sheet/SheetOverlay.vue0.8 kB
<script setup lang="ts"> import type { DialogOverlayProps } from 'reka-ui' import type { HTMLAttributes } from 'vue' import { reactiveOmit } from '@vueuse/core' import { DialogOverlay } from 'reka-ui' import { cn } from '@/lib/utils' const props = defineProps<DialogOverlayProps & { class?: HTMLAttributes['class'] }>() const delegatedProps = reactiveOmit(props, 'class') </script> <template> <DialogOverlay data-uipkge data-slot="sheet-overlay" :class=" cn( 'motion-safe:data-[state=open]:animate-in motion-safe:data-[state=closed]:animate-out motion-safe:data-[state=closed]:fade-out-0 motion-safe:data-[state=open]:fade-in-0 bg-foreground/50 fixed inset-0 z-50', props.class, ) " v-bind="delegatedProps" > <slot /> </DialogOverlay> </template>app/components/ui/sheet/SheetTitle.vue0.6 kB
<script setup lang="ts"> import type { DialogTitleProps } from 'reka-ui' import type { HTMLAttributes } from 'vue' import { reactiveOmit } from '@vueuse/core' import { DialogTitle } from 'reka-ui' import { cn } from '@/lib/utils' const props = defineProps<DialogTitleProps & { class?: HTMLAttributes['class'] }>() const delegatedProps = reactiveOmit(props, 'class') </script> <template> <DialogTitle data-uipkge data-slot="sheet-title" :class="cn('text-foreground font-semibold', props.class)" v-bind="delegatedProps" > <slot /> </DialogTitle> </template>app/components/ui/sheet/SheetTrigger.vue0.3 kB
<script setup lang="ts"> import type { DialogTriggerProps } from 'reka-ui' import { DialogTrigger } from 'reka-ui' const props = defineProps<DialogTriggerProps>() </script> <template> <DialogTrigger data-uipkge data-slot="sheet-trigger" v-bind="props"> <slot /> </DialogTrigger> </template>app/components/ui/sheet/index.ts0.5 kB
export { default as Sheet } from './Sheet.vue' export { default as SheetClose } from './SheetClose.vue' export { default as SheetContent } from './SheetContent.vue' export { default as SheetDescription } from './SheetDescription.vue' export { default as SheetFooter } from './SheetFooter.vue' export { default as SheetHeader } from './SheetHeader.vue' export { default as SheetTitle } from './SheetTitle.vue' export { default as SheetTrigger } from './SheetTrigger.vue'
Raw manifest:https://uipkge.dev/r/vue/sheet.json