Sheet
Vue overlaySide-mounted modal that slides in from the top, right, bottom, or left edge. Use for filter panels, edit drawers, and mobile menus.
Also available for React ->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.json
Or with the named registry:
npx shadcn-vue@latest add @uipkge/sheet
Examples
Dependencies
Used by
Files (10)
-
app/components/ui/sheet/Sheet.vue 0.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.vue 0.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.vue 3 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 fixed z-50 flex flex-col gap-4 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-xs 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.vue 0.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.vue 0.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.vue 0.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.vue 0.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.vue 0.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.vue 0.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.ts 0.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