Skip to content

Popover

Floating panel anchored to a trigger. Built on Reka UI's Popover primitives — floating-ui powers positioning so the panel flips and shifts to stay on-screen.

bash
npm install @vuecs/overlays
vue
<script setup lang="ts">
import {
    VCPopover,
    VCPopoverArrow,
    VCPopoverClose,
    VCPopoverContent,
    VCPopoverTrigger,
} from '@vuecs/overlays';
</script>

<template>
    <VCPopover
        v-for="side in ['top', 'right', 'bottom', 'left'] as const"
        :key="side"
    >
        <VCPopoverTrigger>{{ side }}</VCPopoverTrigger>
        <VCPopoverContent :side="side" :side-offset="8">
            <p class="text-sm font-semibold">{{ side }} popover</p>
            <p class="text-xs text-fg-muted">
                floating-ui flips and shifts to stay on-screen.
            </p>
            <VCPopoverArrow />
            <VCPopoverClose />
        </VCPopoverContent>
    </VCPopover>
</template>
css
@import "tailwindcss";
@import "@vuecs/design";

@custom-variant dark (&:where(.dark, .dark *));

Compound API

ComponentWrapsNotes
VCPopoverPopoverRootHolds open state. v-models open. Pass modal: true for focus-trap behavior.
VCPopoverTriggerPopoverTriggerToggles open.
VCPopoverContentPopoverPortal + PopoverContentFloating panel. inline skips the portal. Position via side / sideOffset / align / alignOffset / avoidCollisions.
VCPopoverArrowPopoverArrowOptional pointer arrow.
VCPopoverClosePopoverCloseClose button. Slotless <VCPopoverClose /> renders the corner-X; with slot content it renders neutrally. Pass icon to force the corner-X with custom content.
vue
<script setup lang="ts">
import {
    VCPopover,
    VCPopoverArrow,
    VCPopoverClose,
    VCPopoverContent,
    VCPopoverTrigger,
} from '@vuecs/overlays';
</script>

<template>
    <VCPopover>
        <VCPopoverTrigger>Open</VCPopoverTrigger>
        <VCPopoverContent side="bottom" :side-offset="8">
            <p class="text-sm">Hello from a popover</p>
            <VCPopoverArrow />
            <VCPopoverClose />
        </VCPopoverContent>
    </VCPopover>
</template>

Theme keys

KeyDefault classNotes
triggervc-popover-trigger
contentvc-popover-contentFloating panel; supports data-state="open|closed" for animation.
arrowvc-popover-arrow
closevc-popover-closeNeutral baseline for <VCPopoverClose> — composes with consumer classes.
closeIconvc-popover-close-iconCorner-X positioning + sizing for <VCPopoverCloseIcon>.

Accessibility

Provided by Reka:

  • Click-outside dismiss
  • Escape key closes
  • Focus trap when modal: true
  • ARIA role="dialog" (popover) with aria-controls on the trigger

Animations

Both theme-tailwind and theme-bootstrap ship enter and exit animations (fade + zoom-95) via @vuecs/design's vanilla-CSS port of tw-animate-css. Tailwind theme uses data-[state=open]: / data-[state=closed]: variant prefixes; BS5 theme uses the dual-state helper class vc-overlay-anim. Reka's PopoverContent wraps with Presence internally so exit animations actually play before unmount. prefers-reduced-motion: reduce disables every animation. Override per-instance via :theme-class="{ content: '...' }".

API Reference

<VCPopover>

Root component; provides context to nested parts. Wraps PopoverRoot.

PropTypeDefaultDescription
openboolean | undefinedundefinedControlled open state. Use v-model:open or pair :open with @update:open.
defaultOpenbooleanfalseInitial open state for uncontrolled usage.
modalbooleanfalseTrap focus inside the popover and disable interaction with outside content. Default differs from <VCModal> — popovers are usually non-modal.
EmitPayloadDescription
update:openbooleanFired on open/close changes.

<VCPopoverTrigger>

Toggles the popover. Wraps PopoverTrigger.

PropTypeDefaultDescription
asstring'button'HTML tag to render.
asChildbooleanfalseRender via the default slot's child element.
themeClassPartial<PopoverThemeClasses>undefinedPer-instance theme override.
themeVariantRecord<string, string | boolean>undefinedPer-instance variant values.

<VCPopoverContent>

Floating panel. Bundles PopoverPortal + PopoverContent. floating-ui handles flip/shift to keep the panel on-screen.

PropTypeDefaultDescription
inlinebooleanfalseSkip the portal and render in-place.
side'top' | 'right' | 'bottom' | 'left''bottom'Preferred side relative to the trigger.
sideOffsetnumber4Distance in pixels between trigger and panel.
align'start' | 'center' | 'end''center'Alignment along the chosen side.
alignOffsetnumber0Offset in pixels along the alignment axis.
avoidCollisionsbooleantrueFlip / shift to stay inside the viewport.
themeClassPartial<PopoverThemeClasses>undefinedPer-instance theme override.
themeVariantRecord<string, string | boolean>undefinedPer-instance variant values.

Reka's extra PopoverContent props (onEscapeKeyDown, onPointerDownOutside, onInteractOutside, etc.) pass through via attrs.

<VCPopoverArrow>

Optional pointer arrow that follows the panel's position. Wraps PopoverArrow.

PropTypeDefaultDescription
widthnumber10Arrow width in pixels.
heightnumber5Arrow height in pixels.
themeClassPartial<PopoverThemeClasses>undefinedPer-instance theme override.
themeVariantRecord<string, string | boolean>undefinedPer-instance variant values.

<VCPopoverClose>

Button that dismisses the popover. Wraps PopoverClose. Picks between the closeIcon slot (pre-styled corner-X) and the close slot (neutral) based on slot content + the icon prop:

  • Slotless (<VCPopoverClose />) — corner-X via closeIcon. Renders the default × glyph.
  • With slot content — neutral close slot, so consumer classes via class= or :theme-class compose cleanly.
  • Explicit icon prop — always reads the closeIcon slot, regardless of slot content.

Auto-applies aria-label="Close" when slotless so screen readers don't announce the bare × glyph as "multiplication sign". Pass an explicit aria-label via attrs to override, or supply visible text content to drop the auto-label.

PropTypeDefaultDescription
asstring'button'HTML tag to render.
asChildbooleanfalseRender via the default slot's child element.
iconbooleanfalseForce the closeIcon (corner-X) slot even when slot content is provided. Not needed for slotless usage.
themeClassPartial<PopoverThemeClasses>undefinedPer-instance theme override.
themeVariantRecord<string, string | boolean>undefinedPer-instance variant values.

Released under the Apache 2.0 License.