UIPopover
UIPopover
A versatile popover component that displays floating content when triggered by user interaction. Supports multiple trigger modes, placements, and customizable content.
<template> <UIPopover :trigger="'click'" :placement="'bottom'" :title="'Popover Title'" :width="300" :showArrow="true" :keepAliveOnHover="true" @update:show="handleShowChange" > <template #trigger> <UIButton> Click me </UIButton> </template>
<div class="p-4"> <h3 class="text-lg font-medium mb-2"> Content Title </h3> <p class="text-gray-600"> Popover content goes here. </p> </div> </UIPopover></template>
<script setup lang="ts">const handleShowChange = (visible: boolean) => { console.log('Popover visibility:', visible)}</script>Props
trigger(‘click’ | ‘hover’ | ‘focus’ | ‘manual’): Trigger modeplacement(‘top’ | ‘top-start’ | ‘top-end’ | ‘bottom’ | ‘bottom-start’ | ‘bottom-end’ | ‘left’ | ‘left-start’ | ‘left-end’ | ‘right’ | ‘right-start’ | ‘right-end’): Popover placementtitle(string): Popover titlewidth(number | string): Popover widthshowArrow(boolean): Show popover arrowdisabled(boolean): Disable popoverarrowPointToCenter(boolean): Arrow points to centerkeepAliveOnHover(boolean): Keep popover open on hoverzIndex(number): Popover z-indexdelay(number): Show/hide delay in millisecondsoffset([number, number]): Offset from triggerhideOnClickOutside(boolean): Hide when clicking outsideappendToBody(boolean): Append to document bodyshow(boolean): Control visibility manuallytransition(string): Custom transition name
Events
update:show: Emitted when popover visibility changesshow: Emitted when popover showshide: Emitted when popover hidesclickOutside: Emitted when clicking outsidebeforeEnter: Emitted before enter transitionafterEnter: Emitted after enter transitionbeforeLeave: Emitted before leave transitionafterLeave: Emitted after leave transition
Slots
trigger: Custom trigger contentdefault: Popover contenttitle: Custom title contentarrow: Custom arrow content
Usage Examples
- Basic Popover:
<template> <UIPopover trigger="hover" placement="top" title="Quick Info" > <template #trigger> <InfoIcon class="w-5 h-5 text-gray-400" /> </template>
<div class="p-3"> Hover content with information </div> </UIPopover></template>- Complex Content with Actions:
<template> <UIPopover trigger="click" placement="bottom-start" :width="320" :keepAliveOnHover="true" > <template #trigger> <UIButton> User Settings </UIButton> </template>
<div class="divide-y"> <div class="p-4"> <div class="flex items-center space-x-3"> <img :src="user.avatar" class="w-10 h-10 rounded-full" /> <div> <div class="font-medium"> {{ user.name }} </div> <div class="text-sm text-gray-500"> {{ user.email }} </div> </div> </div> </div>
<div class="p-2"> <UIButton v-for="action in actions" :key="action.id" variant="ghost" class="w-full justify-start" @click="action.handler" > <component :is="action.icon" class="w-5 h-5 mr-2" /> {{ action.label }} </UIButton> </div> </div> </UIPopover></template>
<script setup>const actions = [ { id: 'profile', label: 'Edit Profile', icon: UserIcon, handler: () => navigateToProfile() }, { id: 'settings', label: 'Settings', icon: CogIcon, handler: () => openSettings() }, { id: 'logout', label: 'Logout', icon: LogoutIcon, handler: () => logout() }]</script>- Form in Popover:
<template> <UIPopover v-model:show="showForm" trigger="manual" placement="right" :width="400" :hideOnClickOutside="false" > <template #trigger> <UIButton @click="showForm = true"> Quick Edit </UIButton> </template>
<form class="p-4" @submit.prevent="handleSubmit" > <h3 class="text-lg font-medium mb-4"> Edit Details </h3>
<div class="space-y-4"> <UIInput v-model="form.name" label="Name" required />
<UIInput v-model="form.email" label="Email" type="email" required />
<div class="flex justify-end space-x-2"> <UIButton variant="ghost" @click="showForm = false" > Cancel </UIButton>
<UIButton type="primary" :loading="saving" > Save Changes </UIButton> </div> </div> </form> </UIPopover></template>Best Practices
-
Positioning:
- Choose appropriate placement
- Consider viewport edges
- Maintain visibility
- Handle overflow
-
User Experience:
- Clear trigger indication
- Appropriate timing
- Smooth transitions
- Proper focus management
-
Accessibility:
- ARIA attributes
- Keyboard navigation
- Focus trapping
- Screen reader support
-
Performance:
- Lazy content loading
- Efficient updates
- Memory management
- Event cleanup
Common Use Cases
- Tooltips with Actions:
<template> <UIPopover trigger="hover" placement="top" :delay="200" > <template #trigger> <div class="text-primary-600 underline"> {{ value }} </div> </template>
<div class="p-2"> <UIButton size="sm" variant="ghost" @click="copyToClipboard" > <CopyIcon class="w-4 h-4 mr-1" /> Copy </UIButton> </div> </UIPopover></template>- Context Menu:
<template> <UIPopover v-model:show="showMenu" trigger="manual" :placement="menuPlacement" > <template #trigger> <div class="w-full" @contextmenu.prevent="showContextMenu" > {{ content }} </div> </template>
<div class="py-1"> <UIButton v-for="option in menuOptions" :key="option.id" variant="ghost" class="w-full justify-start px-4 py-2" @click="option.handler" > {{ option.label }} </UIButton> </div> </UIPopover></template>
<script setup>const showMenu = ref(false)const menuPlacement = ref('bottom')
const showContextMenu = (event: MouseEvent) => { // Calculate placement based on click position const { clientX, clientY } = event menuPlacement.value = calculatePlacement(clientX, clientY) showMenu.value = true}</script>- Multi-Level Navigation:
<template> <UIPopover v-for="menu in menus" :key="menu.id" trigger="hover" :placement="menu.placement" :delay="100" > <template #trigger> <UIButton variant="ghost"> {{ menu.label }} <ChevronRightIcon v-if="menu.children" class="w-4 h-4 ml-2" /> </UIButton> </template>
<div class="py-1 min-w-[200px]"> <template v-if="menu.children"> <UIPopover v-for="child in menu.children" :key="child.id" trigger="hover" placement="right-start" :delay="100" > <template #trigger> <UIButton variant="ghost" class="w-full justify-between px-4 py-2" > {{ child.label }} <ChevronRightIcon v-if="child.children" class="w-4 h-4" /> </UIButton> </template>
<div class="py-1 min-w-[200px]"> <UIButton v-for="subItem in child.children" :key="subItem.id" variant="ghost" class="w-full justify-start px-4 py-2" @click="subItem.handler" > {{ subItem.label }} </UIButton> </div> </UIPopover> </template> </div> </UIPopover></template>