Skip to main content
Vuetify0 Logo
Theme
Mode
Palettes
Accessibility
Vuetify One
Sign in to Vuetify One

Access premium tools across the Vuetify ecosystem — Bin, Play, Studio, and more.

Not a subscriber? See what's included

usePopover

A composable for native popover API behavior with CSS anchor positioning.

Edit this page
Report a Bug
Open issues
View on GitHub
Copy Markdown

PreviewIntermediateSep 6, 2026

Installation

Install the Popover plugin to set an app-wide positioning adapter. Without it, every usePopover() call uses V0PopoverAdapter (CSS anchor positioning). Per-instance adapter still wins over the plugin. Tooltip surfaces can set a different adapter on createTooltipPlugin that only they see.

main.ts
import { createApp } from 'vue'
import { createPopoverPlugin } from '@vuetify/v0'
import { FloatingUIPopoverAdapter } from '@vuetify/v0/popover/adapters/floating-ui'
import App from './App.vue'

const app = createApp(App)

app.use(createPopoverPlugin({ adapter: new FloatingUIPopoverAdapter() }))

app.mount('#app')

Usage

usePopover manages a popover’s open/close state, generates CSS anchor positioning styles, and synchronizes reactive state with native popover toggle events. Spread anchorStyles on the activator, contentAttrs and contentStyles on the content element, call attachAnchor() on the trigger, and attach() on the content to wire the positioning adapter and native popover lifecycle.

vue
<script setup lang="ts">
  import { usePopover } from '@vuetify/v0'
  import { useTemplateRef } from 'vue'

  const trigger = useTemplateRef('trigger')
  const content = useTemplateRef('content')

  const {
    isOpen,
    toggle,
    attach,
    attachAnchor,
    anchorStyles,
    contentAttrs,
    contentStyles,
  } = usePopover({ positionArea: 'bottom' })

  attachAnchor(trigger)
  attach(content)
</script>

<template>
  <button ref="trigger" :style="anchorStyles" @click="toggle">
    {{ isOpen ? 'Close' : 'Open' }}
  </button>

  <div
    ref="content"
    v-bind="contentAttrs"
    :style="contentStyles"
  >
    Popover content
  </div>
</template>

Adapters

Adapters let you swap the underlying positioning engine without changing your application code.

AdapterImportDescription
V0PopoverAdapter@vuetify/v0CSS anchor positioning (default, zero runtime dependency)
FloatingUIPopoverAdapter@vuetify/v0/popover/adapters/floating-uiFloating UI↗︎ JS measurement — flip() covers overflow

FloatingUIPopoverAdapter is subpath-only so @floating-ui/dom stays out of the main barrel. Install the peer, then pass an instance via the adapter option. positionTry is ignored; flip() covers the overflow intent. Pass middleware to the constructor to override the default [offset(8), flip(), shift({ padding: 8 })].

bash
pnpm add @floating-ui/dom
src/popover.ts
import { usePopover } from '@vuetify/v0'
import { FloatingUIPopoverAdapter } from '@vuetify/v0/popover/adapters/floating-ui'

const popover = usePopover({ adapter: new FloatingUIPopoverAdapter() })

App-wide, skip the per-instance option and install the plugin once:

ts
import { createPopoverPlugin } from '@vuetify/v0'
import { FloatingUIPopoverAdapter } from '@vuetify/v0/popover/adapters/floating-ui'

app.use(createPopoverPlugin({ adapter: new FloatingUIPopoverAdapter() }))

Architecture

usePopover builds on useEventListener for native toggle event synchronization. It is a standalone composable — not part of the compound Popover component — making it ideal for building select, combobox, tooltip, and menu components directly.

Popover Architecture

Use controls to zoom and pan. Click outside or press Escape to close.

Popover Architecture

Options

OptionTypeDefaultNotes
idstringautoBase ID for anchor name and popover id. Auto-generated if not provided
positionAreaMaybeRefOrGetter<string>'bottom'CSS position-area value — controls where the content appears relative to the anchor
positionTryMaybeRefOrGetter<string>'most-width bottom'CSS position-try-fallbacks value — fallback positions when the primary area overflows
isOpenRef<boolean>External ref for bidirectional open state (e.g., from defineModel)
openDelayMaybeRefOrGetter<number>0Milliseconds to wait before opening the popover
closeDelayMaybeRefOrGetter<number>0Milliseconds to wait before closing the popover
adapterPopoverAdapternew V0PopoverAdapter()Positioning engine. Resolution: per-instance, then createPopoverPlugin, then CSS anchor positioning — see Adapters

Reactivity

Property/MethodReactiveNotes
isOpenShallowRef, tracks whether the popover is open
open()-Open the popover
close()-Close the popover
toggle()-Toggle open/close
cancel()-Cancel any pending open or close transition
attach(el)-Wire native show/hide watch + toggle event sync to a content element
attachAnchor(el)-Register the activator/reference element with the positioning adapter
anchorStylesReadonly Ref, CSS anchor-name for the activator element
contentAttrsReadonly Ref, id and popover attribute for the content element
contentStylesReadonly Ref, adapter-owned styles for the content element

Examples

A custom account menu built directly on usePopover, without the compound Popover component. The composable owns the popover instance and the menu data, the presentational component renders the trigger and the panel, and the entry wires them together and reports the chosen action. This is the shape to reach for when you want full control over a menu, select, or combobox surface rather than the slots and transitions of Popover.

The example exercises the full three-part spread that usePopover returns. anchorStyles goes on the trigger, where it sets the CSS anchor-name the panel positions against; contentAttrs goes on the panel and applies its id plus the native popover attribute; and contentStyles is adapter-owned — CSS anchor-positioning rules from V0PopoverAdapter by default (position-anchor, position-area, and the position-try-fallbacks produced by positionTry: 'flip-block'), or top/left coordinates from a JS engine such as Floating UI. Because contentAttrs registers an auto popover, the browser handles light dismiss for free: clicking outside or pressing Escape closes the panel.

MenuButton.vue calls attachAnchor(trigger) and attach(content) with template refs to the trigger and panel. attachAnchor registers the reference element with the positioning adapter — Floating UI needs both nodes — and attach wires the native toggle event back into isOpen, so when the browser closes the popover on light dismiss the reactive state stays in sync. Selecting an item calls close() and records the choice; the trigger reads isOpen to rotate its caret. For the close-on-outside-click behavior wired manually rather than through the native popover, see useClickOutside; the open and close delays come from useDelay.

FileRole
useMenu.tsCreates the popover, owns the menu items, and closes on select
MenuButton.vueRenders the trigger and panel; calls attachAnchor and attach
menu-button.vueWires the composable to the component and shows the chosen action

Open the menu and choose an action. Click outside or press Escape to dismiss.

Theme
Mode
Palettes
Accessibility

Bring your own positioning engine

usePopover positions content with CSS anchor positioning by default (V0PopoverAdapter) — no JavaScript measurement, no runtime dependency. For Firefox ESR and Safari before version 26, reach for the shipped FloatingUIPopoverAdapter first. The sketch below is the adapter shape if you want to wrap a different engine (Popper, or your own) rather than import the first-party one. Per-call state lives in the setup() closure so a shared instance stays re-entrant — do not assign this.dispose. Native [popover] is position: fixed with inset: 0; margin: auto, so any JS engine that writes top/left must unset those, and if the engine has a strategy option it must be 'fixed'.

ts
import { IN_BROWSER, isNullOrUndefined, PopoverAdapter } from '@vuetify/v0'
import { onScopeDispose, shallowRef, watch } from 'vue'

import type { PopoverAdapterContext } from '@vuetify/v0'

export class MyPopoverAdapter extends PopoverAdapter {
  setup (context: PopoverAdapterContext) {
    function positionStyles (top: string, left: string): Record<string, string> {
      return {
        'position': 'fixed',
        'margin': 'unset',
        'inset': 'unset',
        top,
        left,
      }
    }

    const styles = shallowRef(positionStyles('0px', '0px'))

    function reposition () {
      if (!IN_BROWSER) return

      const anchor = context.anchorEl.value
      const content = context.contentEl.value

      if (isNullOrUndefined(anchor) || isNullOrUndefined(content)) return
      if (!context.isOpen.value) return

      const rect = anchor.getBoundingClientRect()
      const size = content.getBoundingClientRect()
      const { side, align } = context.placement.value
      const gap = 8

      let top = rect.bottom + gap
      let left = rect.left

      if (side === 'top') {
        top = rect.top - size.height - gap
      } else if (side === 'left') {
        top = rect.top
        left = rect.left - size.width - gap
      } else if (side === 'right') {
        top = rect.top
        left = rect.right + gap
      }

      if (align === 'end') {
        left = rect.right - size.width
      } else if (align === 'center' && (side === 'top' || side === 'bottom')) {
        left = rect.left + (rect.width - size.width) / 2
      }

      styles.value = positionStyles(`${top}px`, `${left}px`)
    }

    const stopWatch = watch(
      [context.anchorEl, context.contentEl, context.isOpen, context.placement],
      reposition,
      { immediate: true },
    )

    onScopeDispose(stopWatch, true)

    return styles
  }
}
ts
import { usePopover } from '@vuetify/v0'
import { MyPopoverAdapter } from './my-popover-adapter'

const popover = usePopover({ adapter: new MyPopoverAdapter() })

Everything else is unchanged — attach(), attachAnchor(), contentAttrs, and anchorStyles all work the same way regardless of which adapter is active. contentStyles becomes whatever the adapter’s setup() returns instead of the CSS anchor-positioning declarations.

The Popover component and the Select, Tooltip, and createCombobox built on top of usePopover all accept the same adapter option (positionAdapter on createCombobox, since it already has its own filtering adapter) and forward it through, so swapping the positioning engine for one of those doesn’t require dropping down to usePopover directly.

FAQ

Discord
Need help? Join our community for support and discussions ↗

API Reference

The following API details are for the usePopover composable.
Was this page helpful?

© 2016-1970 Vuetify, LLC
Services
Ctrl+/