Usage
Use a Button or any other component in the default slot of the Popover.
Then, use the #content slot to add the content displayed when the Popover is open.
<template>
<PPopover>
<PButton label="Open" color="neutral" variant="subtle" />
<template #content>
<CorePlaceholder class="size-48 m-4 inline-flex" />
</template>
</PPopover>
</template>
Mode
Use the mode prop to change the mode of the Popover. Defaults to click.
<template>
<PPopover mode="hover">
<PButton label="Open" color="neutral" variant="subtle" />
<template #content>
<CorePlaceholder class="size-48 m-4 inline-flex" />
</template>
</PPopover>
</template>
Delay
When using the hover mode, you can use the open-delay and close-delay props to control the delay before the Popover is opened or closed.
<template>
<PPopover mode="hover" :open-delay="500" :close-delay="300">
<PButton label="Open" color="neutral" variant="subtle" />
<template #content>
<CorePlaceholder class="size-48 m-4 inline-flex" />
</template>
</PPopover>
</template>
Content
Use the content prop to control how the Popover content is rendered, like its align or side for example.
<template>
<PPopover
:content="{
align: 'center',
side: 'bottom',
sideOffset: 8
}"
>
<PButton label="Open" color="neutral" variant="subtle" />
<template #content>
<CorePlaceholder class="size-48 m-4 inline-flex" />
</template>
</PPopover>
</template>
Arrow
Use the arrow prop to display an arrow on the Popover.
<template>
<PPopover arrow>
<PButton label="Open" color="neutral" variant="subtle" />
<template #content>
<CorePlaceholder class="size-48 m-4 inline-flex" />
</template>
</PPopover>
</template>
Modal
Use the modal prop to control whether the Popover blocks interaction with outside content. Defaults to false.
<template>
<PPopover modal>
<PButton label="Open" color="neutral" variant="subtle" />
<template #content>
<CorePlaceholder class="size-48 m-4 inline-flex" />
</template>
</PPopover>
</template>
Dismissible
Use the dismissible prop to control whether the Popover is dismissible when clicking outside of it or pressing escape. Defaults to true.
close:prevent event will be emitted when the user tries to close it.<script setup lang="ts">
import { ref } from 'vue';
const open = ref(false);
</script>
<template>
<PPopover
v-model:open="open"
:dismissible="false"
:pohon="{ content: 'p-4' }"
>
<PButton
label="Open"
color="neutral"
variant="subtle"
/>
<template #content>
<div class="mb-4 flex gap-4 items-center">
<h2 class="text-highlighted font-semibold">
Popover non-dismissible
</h2>
<PButton
color="neutral"
variant="ghost"
icon="i-lucide:x"
@click="open = false"
/>
</div>
<CorePlaceholder class="size-full min-h-48" />
</template>
</PPopover>
</template>
Examples
Control open state
You can control the open state by using the default-open prop or the v-model:open directive.
<script setup lang="ts">
import { defineShortcuts } from '#imports';
import { ref } from 'vue';
const open = ref(false);
defineShortcuts({
o: () => {
open.value = !open.value;
},
});
</script>
<template>
<PPopover v-model:open="open">
<PButton
label="Open"
color="neutral"
variant="subtle"
/>
<template #content>
<CorePlaceholder class="m-4 inline-flex size-48" />
</template>
</PPopover>
</template>
defineShortcuts, you can toggle the Popover by pressing O.With command palette
You can use a CommandPalette component inside the Popover's content.
<script setup lang="ts">
import { ref } from 'vue';
const items = ref([
{
label: 'bug',
value: 'bug',
chip: {
color: 'error' as const,
},
},
{
label: 'feature',
value: 'feature',
chip: {
color: 'success' as const,
},
},
{
label: 'enhancement',
value: 'enhancement',
chip: {
color: 'info' as const,
},
},
]);
const label = ref([]);
</script>
<template>
<PPopover :content="{ side: 'right', align: 'start' }">
<PButton
icon="i-lucide:tag"
label="Select labels"
color="neutral"
variant="subtle"
/>
<template #content>
<PCommandPalette
v-model="label"
multiple
placeholder="Search labels..."
:groups="[{ id: 'labels', items }]"
:pohon="{ input: '[&>input]:h-8 [&>input]:text-sm' }"
/>
</template>
</PPopover>
</template>
With following cursor
You can make the Popover follow the cursor when hovering over an element using the reference prop:
<script setup lang="ts">
import { computed, ref } from 'vue';
const open = ref(false);
const anchor = ref({ x: 0, y: 0 });
const reference = computed(() => ({
getBoundingClientRect: () =>
({
width: 0,
height: 0,
left: anchor.value.x,
right: anchor.value.x,
top: anchor.value.y,
bottom: anchor.value.y,
...anchor.value,
} as DOMRect),
}));
</script>
<template>
<PPopover
:open="open"
:reference="reference"
:content="{ side: 'top', sideOffset: 16, updatePositionStrategy: 'always' }"
>
<div
class="border-border-accented text-sm border rounded-md border-dashed flex w-72 aspect-video items-center justify-center"
@pointerenter="open = true"
@pointerleave="open = false"
@pointermove="(ev) => {
anchor.x = ev.clientX
anchor.y = ev.clientY
}"
>
Hover me
</div>
<template #content>
<div class="p-4">
{{ anchor.x.toFixed(0) }} - {{ anchor.y.toFixed(0) }}
</div>
</template>
</PPopover>
</template>
With anchor slot
You can use the #anchor slot to position the Popover against a custom element.
mode is click.<script lang="ts" setup>
import { ref } from 'vue';
const open = ref(false);
</script>
<template>
<PPopover
v-model:open="open"
:dismissible="false"
:pohon="{ content: 'w-(--akar-popper-anchor-width) p-4' }"
>
<template #anchor>
<PInput
placeholder="Focus to open"
@focus="open = true"
@blur="open = false"
/>
</template>
<template #content>
<CorePlaceholder class="w-full aspect-square" />
</template>
</PPopover>
</template>
API
Props
| Prop | Default | Type |
|---|---|---|
mode | 'click' | MThe display mode of the popover. |
content | { side: 'bottom', sideOffset: 8, collisionPadding: 8 } | APopoverContentProps & Partial<EmitsToProps<PopoverContentImplEmits>>The content of the popover.
|
arrow | false | boolean | APopoverArrowPropsDisplay an arrow alongside the popover. |
portal | true | string | false | true | HTMLElement
|
reference | Element | VirtualElementThe reference (or anchor) element that is being referred to for positioning. If not provided will use the current component as anchor.
| |
openDelay | 0 | numberThe duration from when the mouse enters the trigger until the hover card opens. |
closeDelay | 0 | numberThe duration from when the mouse leaves the trigger or content until the hover card closes. |
dismissible | true | booleanWhen |
defaultOpen | booleanThe open state of the popover when it is initially rendered. Use when you do not need to control its open state. | |
open | booleanThe controlled open state of the popover. | |
modal | false | booleanThe modality of the popover. When set to true, interaction with outside elements will be disabled and only popover content will be visible to screen readers. |
pohon | { content?: ClassValue; arrow?: ClassValue; } |
Slots
| Slot | Type |
|---|---|
default | { open: boolean; } |
content | SlotProps<M> |
anchor | SlotProps<M> |
close function is only available when mode is set to click because Akar exposes this for APopover but not for AHoverCard.Emits
| Event | Type |
|---|---|
close:prevent | [] |
update:open | [value: boolean] |
Theme
Below is the theme configuration skeleton for the PPopover. Since the component is provided unstyled by default, you will need to fill in these values to apply your own custom look and feel. If you prefer to use our pre-built, opinionated styling, you can instead use our UnoCSS preset, this docs is using it as well.
export default defineAppConfig({
pohon: {
popover: {
slots: {
content: '',
arrow: ''
}
}
}
};
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import pohon from 'pohon-ui/vite'
export default defineAppConfig({
pohon: {
popover: {
slots: {
content: '',
arrow: ''
}
}
}
};
Akar
With Pohon UI, you can achieve similar component functionality with less code and effort, as it comes with built-in styles mechanism and behaviors that are optimized for common use cases. Since it's using unocss-variants it adds a runtime cost, but it can be worth it if you prioritize development speed and ease of use over fine-grained control.
If this is a deal breaker for you, you can always stick to using Akar and build your own custom components on top of it.