Popover
Accessible, unstyled Popover compound primitive.
|
|
| Package |
@vizejs/ui/popover |
| Maturity |
stable |
| Own the source |
vize lib pull popover |
| Requires |
context, controllable-state, dismissable-layer, focus-guards, focus-scope, id, inert-outside, portal, positioner, presence, scroll-lock |
| Aliases |
floating dialog, disclosure layer, coach mark shell, anchored popup |
| Covers |
WAI-ARIA dialog pattern, HTML Popover API authoring model, Radix Popover, Reka UI Popover |
Minimal setup
Use a Vue 3.5+ project with Vue SFC compilation configured (see Vite+ integration).
vp install @vizejs/ui
Add the public import below to <script setup lang="ts">. Components provide behavior without styles; the preview uses optional Paper styles.
import { Popover, PopoverRoot, PopoverArrow, PopoverContent, PopoverTrigger } from "@vizejs/ui/popover";
Or copy the source into your project with vize lib pull popover (see Source Distribution).
Try it
This is the basic example maintained with the component source, using the public package imports. The preview adds the optional Paper styles.
Open the interactive example · Styling these components
Copy the example
<!-- Popover dialog with a labelled heading and a form field, opened via v-model:open. -->
<script setup lang="ts">
import { ref, useId } from "vue";
import { PopoverArrow, PopoverContent, PopoverRoot, PopoverTrigger } from "@vizejs/ui/popover";
const open = ref(false);
const width = ref("1280");
const headingId = useId();
const inputId = useId();
</script>
<template>
<PopoverRoot v-model:open="open">
<PopoverTrigger>Canvas size</PopoverTrigger>
<PopoverContent placement="bottom-start" :aria-labelledby="headingId">
<h3 :id="headingId">Canvas size</h3>
<label :for="inputId">Width in pixels</label>
<input :id="inputId" v-model="width" type="number" min="1" />
<button type="button" @click="() => (open = false)">Done</button>
<PopoverArrow />
</PopoverContent>
</PopoverRoot>
</template>
API
Popover
Source: src/families/overlays/popover/popover-root.vue
Props
| Prop |
Type |
Default |
Description |
id |
string | null |
undefined |
Consumer-owned Popover base id. null and undefined select a deterministic fallback. |
open |
boolean |
undefined |
Controlled open state. undefined selects uncontrolled behavior. |
defaultOpen |
boolean |
false |
Initial open state for uncontrolled use. |
modal |
boolean |
false |
Whether the popover makes outside content inert and focus-contained while open. |
disabled |
boolean |
false |
Disable trigger-driven opening and request closure when already open. |
Events
| Event |
Payload |
Description |
update:open |
[value: boolean] |
Fired when the Popover requests a controlled open value. |
open-change |
[value: boolean, previous: boolean, nativeEvent: Event | null] |
Fired after any distinct open-state request. |
Slots
| Slot |
Slot props |
Description |
default |
PopoverSlotState |
Compound Popover children. Receives the current open, modal, and disabled state. |
Exposed
| Member |
Type |
Description |
close |
— |
|
contentId |
— |
|
disabled |
— |
|
id |
— |
|
modal |
— |
|
open |
— |
|
openPopover |
— |
|
setOpen |
— |
|
state |
— |
|
toggle |
— |
|
triggerId |
— |
|
PopoverRoot
Source: src/families/overlays/popover/popover-root.vue
Props
| Prop |
Type |
Default |
Description |
id |
string | null |
undefined |
Consumer-owned Popover base id. null and undefined select a deterministic fallback. |
open |
boolean |
undefined |
Controlled open state. undefined selects uncontrolled behavior. |
defaultOpen |
boolean |
false |
Initial open state for uncontrolled use. |
modal |
boolean |
false |
Whether the popover makes outside content inert and focus-contained while open. |
disabled |
boolean |
false |
Disable trigger-driven opening and request closure when already open. |
Events
| Event |
Payload |
Description |
update:open |
[value: boolean] |
Fired when the Popover requests a controlled open value. |
open-change |
[value: boolean, previous: boolean, nativeEvent: Event | null] |
Fired after any distinct open-state request. |
Slots
| Slot |
Slot props |
Description |
default |
PopoverSlotState |
Compound Popover children. Receives the current open, modal, and disabled state. |
Exposed
| Member |
Type |
Description |
open |
boolean |
Whether the popover content is currently visible and interactive. |
modal |
boolean |
Whether outside content is inert and focus-contained. |
disabled |
boolean |
Whether trigger-driven opening is disabled by the root. |
state |
PopoverState |
Stable state token for styling and tests. |
id |
string |
Root-owned base id for the compound popover family. |
triggerId |
string |
Id wired to the native trigger button. |
contentId |
string |
Id wired from PopoverTrigger to PopoverContent. |
setOpen |
(value: boolean, event?: Event | null) => boolean |
Request a specific open value and report whether it differs. |
openPopover |
(event?: Event | null) => boolean |
Request the open state. |
close |
(event?: Event | null) => boolean |
Request the closed state. |
toggle |
(event?: Event | null) => boolean |
Request the opposite open state. |
PopoverArrow
Source: src/families/overlays/popover/popover-arrow.vue
Slots
| Slot |
Slot props |
Description |
default |
PopoverArrowSlotState |
Decorative arrow contents. Receives coordinates for optional custom drawing. |
Exposed
| Member |
Type |
Description |
x |
number | null |
Current arrow x coordinate when the positioner can measure it. |
y |
number | null |
Current arrow y coordinate when the positioner can measure it. |
element |
HTMLDivElement | null |
Rendered arrow element. |
PopoverContent
Source: src/families/overlays/popover/popover-content.vue
Props
| Prop |
Type |
Default |
Description |
forceMount |
boolean |
false |
Keep the content mounted while the popover is closed. |
to |
string | HTMLElement |
"body" |
CSS selector or element the popover layer is moved into. |
portalDisabled |
boolean |
false |
Render in place instead of teleporting. |
defer |
boolean |
true |
Keep content in place until the target exists, avoiding SSR mismatch. |
placement |
Placement |
"bottom" |
Preferred placement before collision handling. |
strategy |
PositionerStrategy |
"fixed" |
CSS positioning mode published on the floating host. |
offset |
number |
8 |
Gap on the main axis between trigger and content. |
collisionPadding |
number |
4 |
Viewport padding the floating element should not cross. |
arrowPadding |
number |
0 |
Inset kept between the arrow and floating edges. |
direction |
"ltr" | "rtl" |
"ltr" |
Writing direction used to resolve start/end alignment. |
flip |
boolean |
true |
Flip to the opposite side when the preferred side overflows more. |
shift |
boolean |
true |
Shift the floating box back into the viewport after flip. |
size |
boolean |
false |
Constrain the host and publish positioner available-size CSS variables. |
safeArea |
boolean |
false |
Keep floating content clear of safe-area insets. |
hide |
boolean |
true |
Hide when the trigger no longer intersects the viewport. |
updateOnScroll |
boolean |
true |
Recalculate while ancestors scroll. |
updateOnResize |
boolean |
true |
Recalculate when the document or visual viewport resizes. |
viewport |
Rect |
undefined |
Viewport used for flip, shift, and hide. |
trapFocus |
boolean |
true |
Contain focus inside an open modal popover. |
autoFocus |
boolean |
true |
Move focus into content when it opens. |
restoreFocus |
boolean |
true |
Restore focus when content closes. |
inertOutside |
boolean |
true |
Make outside content inert while the modal popover is open. |
lockScroll |
boolean |
true |
Lock document scroll while the modal popover is open. |
closeOnEscape |
boolean |
true |
Let Escape request dismissal while the popover is open. |
closeOnPointerDownOutside |
boolean |
true |
Let outside pointer-down request dismissal. |
closeOnFocusOutside |
boolean |
true |
Let outside focus movement request dismissal. |
initialFocus |
() => HTMLElement | null | undefined |
undefined |
Preferred initial focus target inside the content. |
ariaLabel |
string |
undefined |
Accessible name when visible text is not enough. |
ariaLabelledby |
string |
undefined |
Space-separated ids that label the popover dialog. |
ariaDescribedby |
string |
undefined |
Space-separated ids that describe the popover dialog. |
role |
"alertdialog" | "dialog" |
"dialog" |
Content role. Use "alertdialog" for confirmations that interrupt the user and need a response. |
Events
| Event |
Payload |
Description |
open-auto-focus |
[event: PopoverAutoFocusEvent] |
Fired before automatic entry focus is applied. |
close-auto-focus |
[event: PopoverAutoFocusEvent] |
Fired before automatic focus restoration is applied. |
escape-key-down |
[event: DismissableLayerEscapeKeyDownEvent] |
Fired before Escape requests dismissal. |
pointer-down-outside |
[event: DismissableLayerPointerDownOutsideEvent] |
Fired before an outside pointer-down requests dismissal. |
focus-outside |
[event: DismissableLayerFocusOutsideEvent] |
Fired before outside focus movement requests dismissal. |
interact-outside |
[event: DismissableLayerInteractOutsideEvent] |
Fired before outside pointer or focus interaction requests dismissal. |
dismiss |
[event: DismissableLayerDismissEvent] |
Fired after an unprevented dismissal request. |
Slots
| Slot |
Slot props |
Description |
default |
PopoverContentSlotState |
Popover content. Receives open state and resolved placement. |
Exposed
| Member |
Type |
Description |
open |
boolean |
Whether the popover content is currently visible and interactive. |
modal |
boolean |
Whether outside content is inert and focus-contained. |
disabled |
boolean |
Whether trigger-driven opening is disabled by the root. |
state |
PopoverState |
Stable state token for styling and tests. |
element |
HTMLDivElement | null |
Rendered popover content element. |
focusFirst |
() => HTMLElement | null |
Focus the first eligible descendant in the popover content. |
focusContent |
(options?: FocusOptions) => void |
Move focus to the content fallback target. |
PopoverTrigger
Source: src/families/overlays/popover/popover-trigger.vue
Props
| Prop |
Type |
Default |
Description |
type |
"button" | "reset" | "submit" |
"button" |
Native button submission behavior. |
disabled |
boolean |
false |
Remove the trigger from activation and sequential keyboard focus. |
ariaLabel |
string |
undefined |
Accessible name when no visible label supplies one. |
Events
| Event |
Payload |
Description |
click |
[nativeEvent: MouseEvent] |
Fired before the trigger toggles the popover. Call preventDefault() to keep state unchanged. |
Slots
| Slot |
Slot props |
Description |
default |
PopoverSlotState |
Trigger contents. Receives the current Popover state and trigger availability. |
Exposed
| Member |
Type |
Description |
element |
HTMLButtonElement | null |
Rendered native button. |
focus |
(options?: FocusOptions) => void |
Move focus to the trigger. |
Behavior
| Surface |
Contract |
| State |
PopoverRoot supports uncontrolled defaultOpen and controlled open with update:open and open-change events; disabled roots request closure and publish data-disabled. |
| Trigger |
PopoverTrigger renders a native button with deterministic ids, aria-haspopup="dialog", aria-expanded, aria-controls, part="trigger", data-state, and disabled data hooks. |
| Content |
PopoverContent composes Portal, Presence, Positioner, DismissableLayer, and optional focus/isolation controllers; it renders role="dialog" (or "alertdialog" via role) with deterministic content ids. |
| Dismissal |
Escape, outside pointer-down, and outside focus request closing unless the preventable callback was canceled; trigger interaction is treated as an inside branch and toggles through state. |
| Focus |
Open content auto-focuses the provided initialFocus, the first eligible descendant, or the content fallback; close restores focus to the trigger when focus restoration is enabled. |
| Modal |
modal content may contain focus, add focus guards, inert outside content, and lock scroll; non-modal content skips those document-isolating effects while preserving dialog semantics. |
| Portal |
Content teleports to to after hydration by default; portalDisabled renders in place and forceMount keeps closed content hidden without activating document controllers. |
| Position |
placement, direction, collision, arrow, safe-area, and size props forward to Positioner; content publishes data-placement, data-side, data-align, and data-top-layer. |
| Arrow |
PopoverArrow is a measured Positioner arrow with part="arrow", Popover state data hooks, and slot coordinates; it is decorative and emits no role or label of its own. |
| Styling |
Popover emits no component CSS beyond scoped empty blocks; consumers style with data-vize-ui, part, data-state, data-side, data-align, and Positioner CSS variables. |
| CSS vars |
PopoverContent forwards Positioner sizing variables, including --vize-ui-positioner-available-width and --vize-ui-positioner-available-height, when size is enabled. |
| SSR |
Server output is deterministic, renders portal content in place, includes no document listeners, and activates dismissal, focus, inert, and scroll-lock controllers only after mount. |
| Component |
State x input |
Outcome |
popover-root.vue |
controlled or uncontrolled open request |
Publishes open, modal, disabled, ids, and data-state; emits state requests only for distinct open changes. |
popover-trigger.vue |
enabled click |
Emits click, then toggles the root when the event was not canceled. |
popover-content.vue |
open |
Renders positioned dialog content, activates dismissable layer, applies focus lifecycle, and exposes focus helpers. |
popover-content.vue |
open modal |
Adds focus guards, inert outside, focus containment, and scroll lock through shared foundation primitives. |
popover-content.vue |
closed and force-mounted |
Keeps content in the DOM with hidden state while document controllers remain inactive. |
popover-arrow.vue |
inside positioned content |
Registers with Positioner, publishes arrow geometry to CSS, and exposes slot coordinates. |
| Event |
Dispatch timing and payload |
open-auto-focus |
Before automatic entry focus; payload is a preventable focus-scope event. |
close-auto-focus |
Before automatic focus restoration; payload is a preventable focus-scope event. |
escape-key-down |
Before Escape dismissal; payload is the preventable dismissable-layer Escape event. |
pointer-down-outside |
Before pointer dismissal; payload is the preventable outside pointer event. |
focus-outside |
Before focus dismissal; payload is the preventable outside focus event. |
interact-outside |
Before pointer or focus dismissal; payload is the preventable outside interaction event. |
dismiss |
After an unprevented dismissal request; payload records reason, target, and native event. |