Popover
Overview
Displays rich content in a portal, triggered by a button. SPopover combines the Aria PopoverRoot/PopoverTrigger/PopoverPositioner/PopoverPopup/PopoverArrow/PopoverClose primitive family (built on the shared Popper + dialog-style dismissable/focus layers) with the popoverVariants style recipe (5 slots, 6 sizes).
Use a popover for contextual, non-critical rich content (menus of actions, settings, help). For navigation menus use dropdown-menu; for a small hover hint use tooltip; for a blocking confirmation use popconfirm or dialog.
Usage
Features
- 🧩 Aria/styled split —
PopoverCompactaggregates the popper positioner, popup, arrow and close;SPopoveronly injects styles and forwards slots/events - 🎯 Placement — full popper
placementcontrol (12 directions) with collision avoidance and side-aware slide animations - 🎭 Modal toggle —
modalcontrols outside-pointer blocking,useHideOthers, body scroll lock and focus trapping - 🔽 Arrow —
showArrowrenders a positioned arrow; configurable viaarrowProps - ❌ Closable — optional
closeslot with a built-inlucide:xicon; Escape and outside interaction dismiss - 📐 6 sizes — xs–2xl
size; per-slotuioverrides - ♿ Accessible —
role="dialog", focus trap + loop,aria-labeld close button,axe-corezero violations
Component family
SPopover(styled) — the entry wrapper;popoverVariantsrecipe with dynamic slot forwardingPopoverRoot(Aria) — the state owner;openviauseControllableState,dir/modal/disabled, provides the popper rootPopoverTrigger(Aria) — aButtonthat toggles the popoverPopoverPositioner/PopoverPositionerImpl(Aria) — the focus-trapped, dismissable, positioned surface (built onPopperPositioner)PopoverPopup(Aria) — the popup bodyPopoverArrow(Aria) — the popper arrowPopoverClose(Aria) — the close<button>, emitscloseand togglesopenPopoverCompact(Aria) — the aggregated composite; composes positioner/popup/arrow/close and exposes the slots
Demos
Basic
API
Aria Layer
10Popover
Props
| Prop | Type | Default | Description |
|---|---|---|---|
class | ClassValue | — | class of popup |
size | ThemeSize | — | Visual size of the component. |
ui | Partial<PopoverUi> | — | Per-slot class overrides for the component. |
placement | import("@/index").Placement | — | Placement. |
showArrow | boolean | — | Whether to show an arrow. |
triggerProps | PopoverTriggerProps | — | Properties forwarded to the trigger element. |
portalProps | PopoverPortalProps | — | Properties forwarded to the portal element. |
positionerProps | PopoverPositionerProps | — | Properties forwarded to the positioner element. |
popupProps | PopoverPopupProps | — | Properties forwarded to the popup element. |
arrowProps | PopoverArrowProps | — | Properties forwarded to the arrow element. |
closeProps | PopoverCloseProps | — | Properties forwarded to the close element. |
dir | Direction | — | — |
open | boolean | — | — |
defaultOpen | boolean | — | — |
modal | boolean | — | — |
disabled | boolean | — | — |
Emits
| Emit Name | Parameters | Description |
|---|---|---|
update:open | boolean | Event handler called when the open state of the dialog changes. |
escapeKeyDown | KeyboardEvent | Event handler called when the escape key is down. Can be prevented. |
pointerDownOutside | PointerDownOutsideEvent | Event handler called when a `pointerdown` event happens outside of the `DismissableLayer`. Can be prevented. |
focusOutside | FocusOutsideEvent | Event handler called when the focus moves outside of the `DismissableLayer`. Can be prevented. |
interactOutside | PointerDownOutsideEvent | FocusOutsideEvent | Event handler called when an interaction happens outside the `DismissableLayer`. Specifically, when a `pointerdown`
event happens outside or focus moves outside of it. Can be prevented. |
openAutoFocus | Event | Event handler called when auto-focusing on open. Can be prevented. |
closeAutoFocus | Event | Event handler called when auto-focusing on close. Can be prevented. |
placed | — | — |
Slots
| Slot Name | Parameters | Description |
|---|---|---|
default | { open: boolean; close: () => void; } | Custom content for the default slot. |
trigger | — | Custom content for the trigger slot. |
close | — | Custom content for the close slot. |
PopoverAnchor
PopoverArrow
PopoverClose
Props
| Prop | Type | Default | Description |
|---|---|---|---|
type | ButtonType | 'button' | The type of the button element. Can be one of 'button', 'submit', or 'reset'. |
disabled | boolean | — | Whether the component is disabled. |
asChild | boolean | — | Change the default rendered element for the one passed as a child, merging their props and behavior. |
as | AsTag | Component | 'div' | The element or component this component should render as. Can be overwrite by `asChild` |
Emits
| Emit Name | Parameters | Description |
|---|---|---|
close | PointerEvent | Emitted when close occurs. |
PopoverCompact
Props
| Prop | Type | Default | Description |
|---|---|---|---|
placement | import("@/index").Placement | — | Placement. |
showArrow | boolean | — | Whether to show an arrow. |
triggerProps | PopoverTriggerProps | — | Properties forwarded to the trigger element. |
portalProps | PopoverPortalProps | — | Properties forwarded to the portal element. |
positionerProps | PopoverPositionerProps | — | Properties forwarded to the positioner element. |
popupProps | PopoverPopupProps | — | Properties forwarded to the popup element. |
arrowProps | PopoverArrowProps | — | Properties forwarded to the arrow element. |
closeProps | PopoverCloseProps | — | Properties forwarded to the close element. |
dir | Direction | — | — |
open | boolean | — | — |
defaultOpen | boolean | — | — |
modal | boolean | — | — |
disabled | boolean | — | — |
Emits
| Emit Name | Parameters | Description |
|---|---|---|
update:open | boolean | Event handler called when the open state of the dialog changes. |
escapeKeyDown | KeyboardEvent | Event handler called when the escape key is down. Can be prevented. |
pointerDownOutside | PointerDownOutsideEvent | Event handler called when a `pointerdown` event happens outside of the `DismissableLayer`. Can be prevented. |
focusOutside | FocusOutsideEvent | Event handler called when the focus moves outside of the `DismissableLayer`. Can be prevented. |
interactOutside | PointerDownOutsideEvent | FocusOutsideEvent | Event handler called when an interaction happens outside the `DismissableLayer`. Specifically, when a `pointerdown`
event happens outside or focus moves outside of it. Can be prevented. |
openAutoFocus | Event | Event handler called when auto-focusing on open. Can be prevented. |
closeAutoFocus | Event | Event handler called when auto-focusing on close. Can be prevented. |
placed | — | — |
Slots
| Slot Name | Parameters | Description |
|---|---|---|
default | { open: boolean; close: () => void; } | Custom content for the default slot. |
trigger | — | Custom content for the trigger slot. |
close | — | Custom content for the close slot. |
PopoverPopup
PopoverPortal
PopoverPositioner
Props
| Prop | Type | Default | Description |
|---|---|---|---|
placement | Placement | undefined | The placement of the floating element.
If used, it will override the `side` and `align` props. |
side | Side | 'bottom' | The preferred side of the trigger to render against when open. Will be reversed when collisions occur and
avoidCollisions is enabled. |
sideOffset | number | 0 | The distance in pixels from the trigger. |
sideFlip | boolean | true | Flip to the opposite side when colliding with boundary. |
align | Align | 'center' | The preferred alignment against the trigger. May change when collisions occur. |
alignOffset | number | 0 | An offset in pixels from the `start` or `end` alignment options. |
alignFlip | boolean | true | Flip alignment when colliding with boundary.
May only occur when `prioritizePosition` is true. |
avoidCollisions | boolean | true | When `true`, overrides the side and align preferences to prevent collisions with boundary edges. |
collisionBoundary | Element | (Element | null)[] | null | [ ] | The element used as the collision boundary. By default this is the viewport, though you can provide additional
element(s) to be included in this check. |
collisionPadding | Padding | 0 | The distance in pixels from the boundary edges where collision detection should occur. Accepts a number (same for
all sides), or a partial padding object, for example: { top: 20, left: 20 }. |
arrowPadding | number | 0 | The padding between the arrow and the edges of the content. If your content has border-radius, this will prevent it
from overflowing the corners. |
hideShiftedArrow | boolean | true | When `true`, hides the arrow when it cannot be centered
to the reference element. |
sticky | 'partial' | 'always' | 'partial' | The sticky behavior on the align axis. `partial` will keep the content in the boundary as long as the trigger is at
least partially in the boundary whilst "always" will keep the content in the boundary regardless. |
hideWhenDetached | boolean | false | Whether to hide the content when the trigger becomes fully occluded. |
positionStrategy | 'fixed' | 'absolute' | 'fixed' | The type of CSS position property to use. |
updatePositionStrategy | 'always' | 'optimized' | 'optimized' | Strategy to update the position of the floating element on every animation frame. |
disableUpdateOnLayoutShift | boolean | false | Whether to disable the update position for the content when the layout shifted. |
prioritizePosition | boolean | false | Force content to be position within the viewport. Might overlap the reference element, which may not be desired. |
reference | ReferenceElement | — | The custom element or virtual element that will be set as the reference to position the floating element. If
provided, it will replace the default anchor element. |
trapFocus | boolean | modal | Whether focus is trapped inside the popup while open (Tab cycles within the layer and body
scroll is locked for modal layers). |
disableHoverableContent | boolean | false | When `true`, hovering the popup closes instead of keeping it open (the grace area is disabled,
so leaving the trigger closes the layer immediately). |
onGracePointerExit | (() => void) | — | Callback invoked when the pointer finally leaves the grace area. Lets an upper layer (e.g.
Tooltip) run area-scoped close logic without registering its own second grace area. |
forceMount | boolean | — | Used to force mounting when more control is needed.
Useful when controlling animation with Vue animation libraries. |
Emits
| Emit Name | Parameters | Description |
|---|---|---|
escapeKeyDown | KeyboardEvent | Event handler called when the escape key is down. Can be prevented. |
pointerDownOutside | PointerDownOutsideEvent | Event handler called when a `pointerdown` event happens outside of the `DismissableLayer`. Can be prevented. |
focusOutside | FocusOutsideEvent | Event handler called when the focus moves outside of the `DismissableLayer`. Can be prevented. |
interactOutside | PointerDownOutsideEvent | FocusOutsideEvent | Event handler called when an interaction happens outside the `DismissableLayer`. Specifically, when a `pointerdown`
event happens outside or focus moves outside of it. Can be prevented. |
openAutoFocus | Event | Event handler called when auto-focusing on open. Can be prevented. |
closeAutoFocus | Event | Event handler called when auto-focusing on close. Can be prevented. |
placed | — | — |
PopoverRoot
Props
| Prop | Type | Default | Description |
|---|---|---|---|
dir | Direction | — | — |
open | boolean | — | — |
defaultOpen | boolean | — | — |
modal | boolean | — | — |
disabled | boolean | — | — |
Emits
| Emit Name | Parameters | Description |
|---|---|---|
update:open | boolean | Event handler called when the open state of the dialog changes. |
PopoverTrigger
Props
| Prop | Type | Default | Description |
|---|---|---|---|
type | ButtonType | 'button' | The type of the button element. Can be one of 'button', 'submit', or 'reset'. |
disabled | boolean | — | Whether the component is disabled. |
asChild | boolean | — | Change the default rendered element for the one passed as a child, merging their props and behavior. |
as | AsTag | Component | 'div' | The element or component this component should render as. Can be overwrite by `asChild` |
Notes
Architecture and benchmark differences
PopoverCompact owns the positioner/popup/arrow/close composition while every primitive stays style-free and only the UI wrapper injects the popoverVariants classes. This mirrors radix-ui/shadcn-ui’s headless/styled split, built on the shared Popper primitives. Ant Design, Element Plus, Mantine and Naive UI ship a single styled popover with placement/trigger/width props; VeanUI additionally exposes per-slot *Props, a size scale, an arrow toggle, and a modal mode the single-package popovers generally lack.
| Capability | VeanUI | shadcn/ui | Ant Design Popover | Element Plus Popover | Mantine Popover | Naive UI Popover |
|---|---|---|---|---|---|---|
| Aria/styled split | ✅ | ✅ | — | — | — | — |
| Popper placement (12) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Arrow | ✅ | ✅ | — | — | — | — |
| Modal mode | ✅ | ✅ | — | — | — | — |
| Close button | ✅ | ✅ | — | — | — | — |
| Sizes (6) | ✅ | — | — | — | — | — |
| Focus trap + loop | ✅ | ✅ | — | — | — | — |
— = unsupported or a different interaction model.
Cautions
PopoverRootdefaults to modal (true): opening it blocks outside pointer events and locks body scroll. Passmodal={false}for a lightweight, non-blocking popover.- The popup is rendered through a
Portal(teleported todocument.body); position it inside a relatively-positioned ancestor only withportalProps: { disabled: true }. showArrowrenders an arrow;arrowPropscan override its position/classes.- Provide a
closeslot to show the close control; it defaults to alucide:xicon. - Unlike
tooltip, the popover does not auto-dismiss on pointer-leave; it stays open until dismissed.
Roadmap
N/A — popover is feature-complete for the current parity set.
FAQ
How do I position the popover?
Use placement (from popper), e.g. top, bottom-start, right-end:
<SPopover placement="top">
<template #trigger><SButton>Info</SButton></template>
</SPopover>How do I add a close button?
Provide the close slot:
<SPopover>
<template #trigger><SButton>More</SButton></template>
<template #close>Dismiss</template>
</SPopover>How do I make a non-blocking popover?
Set modal={false}:
<SPopover :modal="false">
<template #trigger><SButton>Open</SButton></template>
</SPopover>How do I control the open state?
Bind open with v-model or use defaultOpen:
<SPopover v-model:open="open">
<template #trigger><SButton>Open</SButton></template>
</SPopover>