Popover
Vue overlayClick-triggered floating panel anchored to a trigger element. Supports optional localStorage persistence and configurable dismissal (click-outside, escape, manual). Built on reka-ui with collision detection.
Also available for React ->Installation
$ pnpm dlx shadcn-vue@latest add https://uipkge.dev/r/vue/popover.json$ npx shadcn-vue@latest add https://uipkge.dev/r/vue/popover.json$ yarn dlx shadcn-vue@latest add https://uipkge.dev/r/vue/popover.json$ bunx shadcn-vue@latest add https://uipkge.dev/r/vue/popover.json
Or with the named registry:
npx shadcn-vue@latest add @uipkge/popover
Examples
Props
| Name | Type / Values | Default | Required |
|---|---|---|---|
persist | string | boolean | false | optional |
closeBehavior | PopoverCloseBehavior | 'auto' | optional |
Schema
Type aliases from this item's source — use them to shape the data you pass in.
PopoverContext interface PopoverContext {
closeBehavior: Ref<PopoverCloseBehavior>
} Dependencies
Used by
Files (6)
-
app/components/ui/popover/Popover.vue 1.8 kB
<script setup lang="ts"> import type { PopoverRootEmits, PopoverRootProps } from 'reka-ui' import { PopoverRoot, useForwardPropsEmits } from 'reka-ui' import { computed, onMounted, provide, ref, toRef, useId, watch } from 'vue' import { POPOVER_INJECTION_KEY, type PopoverCloseBehavior } from './context' const props = withDefaults( defineProps< PopoverRootProps & { persist?: string | boolean closeBehavior?: PopoverCloseBehavior } >(), { persist: false, closeBehavior: 'auto', }, ) const emits = defineEmits<PopoverRootEmits>() provide(POPOVER_INJECTION_KEY, { closeBehavior: toRef(props, 'closeBehavior'), }) const autoId = useId() const storageKey = computed(() => { if (props.persist === false) return null if (props.persist === true) return `uipkge-popover-${autoId}` return props.persist }) const localOpen = ref<boolean>(props.defaultOpen ?? false) onMounted(() => { if (!storageKey.value || typeof localStorage === 'undefined') return if (localStorage.getItem(storageKey.value) === '1') { localOpen.value = true if (props.open === undefined) { emits('update:open', true) } } }) watch( () => (props.open === undefined ? localOpen.value : props.open), (v) => { if (!storageKey.value || typeof localStorage === 'undefined') return if (v) localStorage.setItem(storageKey.value, '1') else localStorage.removeItem(storageKey.value) }, ) const forwarded = useForwardPropsEmits( computed(() => ({ open: props.open, defaultOpen: props.defaultOpen, modal: props.modal, })), emits, ) function onUpdateOpen(value: boolean) { localOpen.value = value emits('update:open', value) } </script> <template> <PopoverRoot v-slot="slotProps" data-uipkge data-slot="popover" v-bind="forwarded" @update:open="onUpdateOpen"> <slot v-bind="slotProps" /> </PopoverRoot> </template> -
app/components/ui/popover/PopoverAnchor.vue 0.3 kB
<script setup lang="ts"> import type { PopoverAnchorProps } from 'reka-ui' import { PopoverAnchor } from 'reka-ui' const props = defineProps<PopoverAnchorProps>() </script> <template> <PopoverAnchor data-uipkge data-slot="popover-anchor" v-bind="props"> <slot /> </PopoverAnchor> </template> -
app/components/ui/popover/PopoverContent.vue 2.1 kB
<script setup lang="ts"> import type { PopoverContentEmits, PopoverContentProps } from 'reka-ui' import type { HTMLAttributes } from 'vue' import { reactiveOmit } from '@vueuse/core' import { PopoverContent, PopoverPortal, useForwardPropsEmits } from 'reka-ui' import { inject } from 'vue' import { cn } from '@/lib/utils' import { POPOVER_INJECTION_KEY } from './context' defineOptions({ inheritAttrs: false, }) const props = withDefaults(defineProps<PopoverContentProps & { class?: HTMLAttributes['class'] }>(), { align: 'center', sideOffset: 4, }) const emits = defineEmits<PopoverContentEmits>() const ctx = inject(POPOVER_INJECTION_KEY, null) const delegatedProps = reactiveOmit(props, 'class') const forwarded = useForwardPropsEmits(delegatedProps, emits) function onPointerDownOutside(e: Event) { const mode = ctx?.closeBehavior.value ?? 'auto' if (mode === 'esc' || mode === 'manual' || mode === 'none') { e.preventDefault() } } function onEscapeKeyDown(e: KeyboardEvent) { const mode = ctx?.closeBehavior.value ?? 'auto' if (mode === 'click-outside' || mode === 'manual' || mode === 'none') { e.preventDefault() } } </script> <template> <PopoverPortal> <PopoverContent data-uipkge data-slot="popover-content" v-bind="{ ...$attrs, ...forwarded }" :class=" cn( 'bg-popover text-popover-foreground 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 motion-safe:data-[state=closed]:zoom-out-95 motion-safe:data-[state=open]:zoom-in-95 motion-safe:data-[side=bottom]:slide-in-from-top-2 motion-safe:data-[side=left]:slide-in-from-right-2 motion-safe:data-[side=right]:slide-in-from-left-2 motion-safe:data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--reka-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden', props.class, ) " @pointer-down-outside="onPointerDownOutside" @escape-key-down="onEscapeKeyDown" > <slot /> </PopoverContent> </PopoverPortal> </template> -
app/components/ui/popover/PopoverTrigger.vue 0.3 kB
<script setup lang="ts"> import type { PopoverTriggerProps } from 'reka-ui' import { PopoverTrigger } from 'reka-ui' const props = defineProps<PopoverTriggerProps>() </script> <template> <PopoverTrigger data-uipkge data-slot="popover-trigger" v-bind="props"> <slot /> </PopoverTrigger> </template> -
app/components/ui/popover/context.ts 0.3 kB
import type { InjectionKey, Ref } from 'vue' export type PopoverCloseBehavior = 'auto' | 'click-outside' | 'esc' | 'manual' | 'none' export interface PopoverContext { closeBehavior: Ref<PopoverCloseBehavior> } export const POPOVER_INJECTION_KEY: InjectionKey<PopoverContext> = Symbol('uipkge-popover') -
app/components/ui/popover/index.ts 0.2 kB
export { default as Popover } from './Popover.vue' export { default as PopoverAnchor } from './PopoverAnchor.vue' export { default as PopoverContent } from './PopoverContent.vue' export { default as PopoverTrigger } from './PopoverTrigger.vue'
Raw manifest: https://uipkge.dev/r/vue/popover.json