Skip to content

Popover

A floating card triggered by hover、click、focus or contextmenu. Built on top of Tooltip.

Basic Usage

Use placement to set the position of the popover, and use content to set the content of the popover.

vue
<template>
  <div class="max-w-600px mx-auto">
    <div class="flex justify-center">
      <re-popover placement="top-start" content="Top Start Text Prompt" title="Title">
        <re-button>top-start</re-button>
      </re-popover>
      <re-popover placement="top" content="Top Center Text Prompt" title="Title">
        <re-button>top</re-button>
      </re-popover>
      <re-popover placement="top-end" content="Top End Text Prompt" title="Title">
        <re-button>top-end</re-button>
      </re-popover>
    </div>
    <div class="flex justify-between">
      <div class="flex flex-col items-start">
        <re-popover
          placement="left-start"
          width="120"
          content="Left Start Text Prompt"
          title="Title"
        >
          <re-button>left-start</re-button>
        </re-popover>
        <re-popover placement="left" width="120px" content="Left Middle Text Prompt" title="Title">
          <re-button class="mt-1em ml-0!">left</re-button>
        </re-popover>
        <re-popover placement="left-end" width="120px" content="Left End Text Prompt" title="Title">
          <re-button class="mt-1em ml-0!">left-end</re-button>
        </re-popover>
      </div>
      <div class="flex flex-col items-end">
        <re-popover
          placement="right-start"
          width="120px"
          content="Right Start Text Prompt"
          title="Title"
        >
          <re-button>right-start</re-button>
        </re-popover>
        <re-popover
          placement="right"
          width="120px"
          content="Right Center Text Prompt"
          title="Title"
        >
          <re-button class="mt-1em ml-0!">right</re-button>
        </re-popover>
        <re-popover
          placement="right-end"
          width="120px"
          content="Right End Text Prompt"
          title="Title"
        >
          <re-button class="mt-1em ml-0!">right-end</re-button>
        </re-popover>
      </div>
    </div>
    <div class="flex justify-center">
      <re-popover placement="bottom-start" content="Bottom Start Text Prompt" title="Title">
        <re-button>bottom-start</re-button>
      </re-popover>
      <re-popover placement="bottom" content="Bottom Center Text Prompt" title="Title">
        <re-button>bottom</re-button>
      </re-popover>
      <re-popover placement="bottom-end" content="Bottom End Text Prompt" title="Title">
        <re-button>bottom-end</re-button>
      </re-popover>
    </div>
  </div>
</template>

Title

Use title or slot#title to provide the title.

vue
<template>
  <re-popover
    content="Content text Content text Content text Content"
    title="This is a Title"
    max-width="200"
    trigger="hover"
  >
    <re-button>Hover me</re-button>
  </re-popover>
  <re-popover
    content="Content text Content text Content text Content"
    max-width="200"
    trigger="hover"
  >
    <template #title>
      <div style="color: #9d87ff; padding: 0.5em 1em; font-size: 16px">This is a Slot Title</div>
    </template>
    <re-button>Slot Title</re-button>
  </re-popover>
</template>

Trigger

Use hoverclickfocus and contextmenu to display Popover by trigger attribute.

vue
<template>
  <re-popover
    content="Popover text Popover text Popover text"
    width="200"
    title="Title"
    trigger="hover"
  >
    <re-button>Hover</re-button>
  </re-popover>
  <re-popover
    content="Popover text Popover text Popover text"
    width="200"
    title="Title"
    trigger="click"
  >
    <re-button>Click</re-button>
  </re-popover>
  <re-popover
    content="Popover text Popover text Popover text"
    width="200"
    title="Title"
    trigger="focus"
  >
    <re-button>Focus</re-button>
  </re-popover>
  <re-popover
    content="Popover text Popover text Popover text"
    width="200"
    title="Title"
    trigger="contextmenu"
    placement="right"
  >
    <re-button>Contextmenu</re-button>
  </re-popover>
</template>

Content

Use content or slot#content to set Popover content, is-html for render HTML content.

vue
<template>
  <re-popover content="<i>Text Content</i>" title="Title" trigger="hover">
    <re-button>Text Content</re-button>
  </re-popover>
  <re-popover content="<i>HTML Content</i>" is-html title="Title" trigger="hover">
    <re-button>HTML Content</re-button>
  </re-popover>
  <re-popover title="Title" trigger="hover">
    <re-button>Slot Content</re-button>
    <template #content>
      <div style="color: #9d87ff; padding: 0 1em 1em">The Slot Content</div>
    </template>
  </re-popover>
</template>

Disabled

Use disabled to disable Popover.

vue
<template>
  <div>
    <re-popover
      :disabled="disabled"
      content="Disabled Popover Disabled Popover Disabled Popover"
      width="200"
      title="Title"
      trigger="hover"
    >
      <re-button type="success" :disabled="disabled">
        {{ disabled ? 'Disabled' : 'Enabled' }}
      </re-button>
    </re-popover>

    <re-button class="ml-5em!" @click="toggle">Click to toggle</re-button>
  </div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'

const disabled = ref(false)

const toggle = () => {
  disabled.value = !disabled.value
}
</script>

Customize Boundary

Use boundary and rootBoundary to set Popover boundary.

vue
<template>
  <div>
    <div class="flex flex-wrap">
      <div
        ref="container1"
        class="h-300px grow basis-300px of-x-hidden of-y-auto text-center demo-border"
      >
        <div class="h-300px"></div>
        <re-popover
          v-if="container1"
          :boundary="container1"
          content="Scroll me to top boundary"
          title="Title"
          width="200"
          :visible="visible"
        >
          <re-button>boundary: container</re-button>
        </re-popover>
        <div class="h-300px"></div>
      </div>
      <div
        ref="container2"
        class="h-300px grow flex basis-300px items-center of-x-auto demo-border"
      >
        <div class="w-500px shrink-0"></div>
        <re-popover
          v-if="container2"
          content="Scroll me to left boundary"
          title="Title"
          width="200"
          :boundary="container2"
          :visible="visible"
          placement="left"
        >
          <re-button>boundary: container</re-button>
        </re-popover>
        <div class="w-500px shrink-0"></div>
      </div>
    </div>
    <div
      ref="container3"
      class="h-300px flex-1 of-hidden of-y-auto shrink-0 text-center demo-border"
    >
      <div class="h-300px"></div>
      <re-popover
        content="Scroll me to body top boundary"
        title="Title"
        width="200"
        :visible="visible"
      >
        <re-button>rootBoundary: 'viewport'</re-button>
      </re-popover>
      <div class="h-300px"></div>
    </div>
  </div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'

const visible = ref<boolean>(true)

const container1 = ref()
const container2 = ref()
const container3 = ref()

onMounted(() => {
  container1.value.scrollTo(0, 160)
  container2.value.scrollTo(220, 0)
  container3.value.scrollTo(0, 160)
})
</script>

Flip

By default, the Popover layer will auto flip based on the viewport, but when one of the below:
1、boundary: 'clippingAncestors' and altBoundary: true.
2、elementContext: 'reference'.
it will flip based on the parent container of the reference which is overflow: hidden/auto/scroll.
Otherwise, use flipOptions attribute for more behaviors.

vue
<template>
  <div class="flex flex-wrap">
    <div
      ref="container1"
      class="h-300px grow basis-300px of-hidden of-y-auto shrink-0 text-center demo-border"
    >
      <div class="h-300px"></div>
      <re-popover
        v-model:visible="visible"
        alt-boundary
        content="Scroll me to top boundary"
        title="Title"
        width="200"
      >
        <re-button>altBoundary: true</re-button>
      </re-popover>
      <div class="h-300px"></div>
    </div>
    <div
      ref="container2"
      class="h-300px grow basis-300px flex items-center min-w-0 of-x-auto demo-border"
    >
      <div class="w-500px shrink-0"></div>
      <re-popover
        content="Scroll me to left boundary"
        alt-boundary
        :visible="visible"
        placement="left"
        title="Title"
        width="200"
      >
        <re-button>altBoundary: true</re-button>
      </re-popover>
      <div class="w-500px shrink-0"></div>
    </div>
  </div>
  <div class="flex flex-wrap">
    <div
      ref="container3"
      class="h-300px grow basis-300px of-hidden of-y-auto shrink-0 text-center demo-border"
    >
      <div class="h-300px"></div>
      <re-popover
        element-context="reference"
        content="elementContext: 'reference'"
        title="Title"
        width="200"
        :visible="visible"
      >
        <re-button>Scroll me to top boundary</re-button>
      </re-popover>
      <div class="h-300px"></div>
    </div>
    <div
      ref="container4"
      class="h-300px grow basis-300px flex items-center min-w-0 of-x-auto demo-border"
    >
      <div class="w-500px shrink-0"></div>
      <re-popover
        content="elementContext: 'reference'"
        element-context="reference"
        :visible="visible"
        placement="left"
        title="Title"
        width="200"
      >
        <re-button>Scroll me to left boundary</re-button>
      </re-popover>
      <div class="w-500px shrink-0"></div>
    </div>
  </div>
  <div class="flex flex-wrap">
    <div
      ref="container5"
      class="h-300px grow basis-300px of-hidden of-y-auto shrink-0 text-center demo-border"
    >
      <div class="h-300px"></div>
      <re-popover
        alt-boundary
        :flip-options="{ fallbackPlacements: ['top', 'right', 'bottom'] }"
        content="Scroll me to top boundary"
        title="Title"
        width="200"
        :visible="visible"
      >
        <re-button class="mr-15em">altBoundary: true</re-button>
      </re-popover>
      <div class="h-300px"></div>
    </div>
    <div
      ref="container6"
      class="h-300px grow basis-300px flex items-center min-w-0 of-x-auto demo-border"
    >
      <div class="w-500px shrink-0"></div>
      <re-popover
        content="Scroll me to left boundary"
        alt-boundary
        :flip-options="{ fallbackPlacements: ['top', 'right', 'bottom'] }"
        :visible="visible"
        placement="left"
        title="Title"
        width="200"
      >
        <re-button>altBoundary: true</re-button>
      </re-popover>
      <div class="w-500px shrink-0"></div>
    </div>
  </div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'

const visible = ref<boolean>(true)
const container1 = ref()
const container2 = ref()
const container3 = ref()
const container4 = ref()
const container5 = ref()
const container6 = ref()

onMounted(() => {
  container1.value.scrollTo(0, 160)
  container2.value.scrollTo(220, 0)
  container3.value.scrollTo(0, 160)
  container4.value.scrollTo(220, 0)
  container5.value.scrollTo(0, 160)
  container6.value.scrollTo(220, 0)
})
</script>

External Reference

Use is-external to activate external-ref, this allows you to place the reference element outside the Popover.

Basic External Trigger Reference

Multiple External Trigger References for singleton

vue
<template>
  <p>Basic External Trigger Reference</p>
  <re-popover
    content="Popover text Popover text Popover text"
    trigger="hover"
    is-external
    :external-ref="externalRef"
    title="Title"
    width="200"
  ></re-popover>
  <re-button ref="buttonRef" @mouseenter="setExternal">Hover me</re-button>

  <p>Multiple External Trigger References for singleton</p>
  <re-popover
    content="Popover text Popover text Popover text"
    trigger="hover"
    title="Title"
    width="200"
    is-external
    :external-ref="hoverRef"
  ></re-popover>
  <re-button ref="buttonRef1" @mouseenter="setRef1()">Hover me 1</re-button>
  <re-button ref="buttonRef2" @mouseenter="setRef2()">Hover me 2</re-button>
  <re-button ref="buttonRef3" @mouseenter="setRef3()">Hover me 3</re-button>
  <p></p>
  <re-popover
    content="Popover text Popover text Popover text"
    trigger="click"
    title="Title"
    width="200"
    is-external
    :external-ref="clickRef"
  ></re-popover>
  <re-button ref="buttonRef4" @click="setRef4()">Click me 1</re-button>
  <re-button ref="buttonRef5" @click="setRef5()">Click me 2</re-button>
  <re-button ref="buttonRef6" @click="setRef6()">Click me 3</re-button>
</template>
<script lang="ts" setup>
import { ref } from 'vue'

const buttonRef = ref()
const externalRef = ref()

const setExternal = () => {
  externalRef.value = buttonRef.value
}

const buttonRef1 = ref()
const buttonRef2 = ref()
const buttonRef3 = ref()

const hoverRef = ref()

const setRef1 = () => {
  hoverRef.value = buttonRef1.value
}
const setRef2 = () => {
  hoverRef.value = buttonRef2.value
}
const setRef3 = () => {
  hoverRef.value = buttonRef3.value
}

const buttonRef4 = ref()
const buttonRef5 = ref()
const buttonRef6 = ref()

const clickRef = ref()

const setRef4 = () => {
  clickRef.value = buttonRef4.value
}
const setRef5 = () => {
  clickRef.value = buttonRef5.value
}
const setRef6 = () => {
  clickRef.value = buttonRef6.value
}
</script>

Virtual Reference

Use is-virtual to activate virtual-ref, it accepts an object with layout properties to build the reference, visible maybe is needed.

vue
<template>
  <re-popover
    content="Popover text Popover text Popover text"
    :visible="visible"
    is-virtual
    :virtual-ref="virtualRef"
    width="200"
    title="Title"
  ></re-popover>
  <re-button @click="visible = !visible">Click me</re-button>
</template>
<script lang="ts" setup>
import { ref, onMounted, onUnmounted } from 'vue'

const visible = ref<boolean>(false)

const position = ref<Record<string, number>>({
  width: 0,
  height: 0,
  x: 0,
  y: 0,
  top: 0,
  left: 0,
  right: 0,
  bottom: 0
})

const virtualRef = ref({
  getBoundingClientRect: () => position.value
})

const move = ({ clientX, clientY }: MouseEvent) => {
  position.value = {
    width: 0,
    height: 0,
    x: clientX,
    y: clientY,
    top: clientY,
    left: clientX,
    right: clientX,
    bottom: clientY
  }
}

onMounted(() => {
  document.addEventListener('mousemove', move)
})
onUnmounted(() => {
  document.removeEventListener('mousemove', move)
})
</script>

Selection Range Build A Virtual Reference

Use is-virtual to activate virtual-ref, it also allows a selection Range to build the reference.

Select the text to visible the Popover
vue
<template>
  <re-popover
    content="Popover text Popover text Popover text"
    :visible="visible"
    is-virtual
    :virtual-ref="rangeRef"
    width="200"
    title="Title"
  ></re-popover>
  <span ref="textRef">Select the text to visible the Popover</span>
</template>
<script lang="ts" setup>
import { ref, onMounted, onBeforeUnmount } from 'vue'
const visible = ref<boolean>(false)

const textRef = ref<HTMLSpanElement | null>(null)
const range = ref<Range | null>()

const rangeRef = {
  getBoundingClientRect: () => range.value?.getBoundingClientRect(),
  getClientRects: () => range.value?.getClientRects()
}

const mouseup = () => {
  range.value?.selectNode(textRef.value!)
  const selection = window.getSelection()

  if (!textRef.value || !selection || selection.rangeCount === 0) return

  const currentRange = selection.getRangeAt(0)
  const container = currentRange.commonAncestorContainer
  const text = selection.toString().trim()

  // Ensure the selection originates from the target element
  if (textRef.value.contains(container!)) {
    range.value = currentRange.cloneRange()
    visible.value = !!text
  }
}

const selectionChange = () => {
  const selection = window.getSelection()
  const text = selection?.toString()?.trim()

  if (!text) {
    visible.value = false
  }
}

onMounted(() => {
  document.addEventListener('mouseup', mouseup)
  document.addEventListener('selectionchange', selectionChange)
})

onBeforeUnmount(() => {
  document.removeEventListener('mouseup', mouseup)
  document.removeEventListener('selectionchange', selectionChange)
})
</script>

Popover Attributes

NameDescriptionTypeDefault
placementPopover positionenumtop
triggerThe trigger event nameenumhover
titlePopover titlestring''
contentPopover contentstring''
is-htmlWhether content is treated as HTML stringboolean
disabledWhether Popover is disabledboolean
visible / v-model:visibleWhether Popover is visibleboolean
offsetOffset from the reference edgenumber10
widthPopover widthnumber | string
heightPopover heightnumber | string
max-widthPopover max widthnumber | string
max-heightPopover max heightnumber | string
themePopover theme'dark' |'light'light
reverse-themewhether to reverse Popover theme colorbooleanfalse
append-toTarget element for appending PopovercssSelector | HTMLElementbody
teleportedWhether to teleport Popover to the appendTo targetbooleantrue
persistentWhether to keep the floating open on hoverbooleantrue
floating-classCustom class for the floatingunion
floating-styleCustom style for the floatingunion
show-delayDelay(ms) before Popover appears , but not effective when visible is present or is-virtual or is-externalnumber0
hide-delayDelay(ms) before Popover disappears , but not effective when visible is present or is-virtual or is-externalnumber200
show-arrowWhether to show arrow for Popoverbooleantrue
arrow-sizeThe arrow sizenumber8
arrow-offsetPadding between the arrow and the Popover edgenumber4
arrow-optionsFloating UI ArrowOptions Parametersinterface
transitionName of Vue3 Transition componentstringpopover
css-transitionCss of Vue3 Transition componentbooleantrue
css-durationDuration of Vue3 Transition componentnumber0
boundaryBoundary of Floating UI DetectOverflowOptions : interfaceunionclippingAncestors
alt-boundaryAltBoundary of Floating UI DetectOverflowOptionsbooleanfalse
root-boundaryRootBoundary of Floating UI DetectOverflowOptionsunionviewport
element-contextElementContext of Floating UI DetectOverflowOptions'reference' | 'floating'floating
flip-offsetOffset from boundary before flipping, Padding of Floating UI DetectOverflowOptionsunion8
flip-optionsFloating UI FlipOptions Parametersinterface
inline-optionsFloating UI InlineOptions Parametersinterface
is-externalWhether external reference is enabledboolean
external-refExternal reference targetHTMLElement
is-virtualWhether virtual reference is enabledboolean
virtual-refVirtual reference object for Floating UI VirtualElementinterface

Popover Slots

NameDescription
defaultCustomize default reference
titleCustomize the title
contentCustomize default content

Popover Exposes

NameDescriptionType
updateManually updates the Popover positionFunction
showManually show the PopoverFunction
hideManually hide the PopoverFunction
triggerRefThe trigger refernce elementHTMLElement
floatingRefThe Popover layer elementHTMLElement
arrowRefThe Popover arrow elementHTMLElement

MIT License