Popover

Displays rich content in a portal, anchored to a trigger element

Usage

import { Popover } from '@hareui/vue'
<script setup lang="ts">import { Button, Popover } from '@hareui/vue'</script><template>  <div class="flex items-center gap-4">    <Popover title="Popover Title" class="max-w-64">      <Button>Click me</Button>      <template #content>        <p class="mt-2 text-sm text-muted">          This is the popover content. You can put any content here.        </p>      </template>    </Popover>  </div></template>

Anatomy

The default slot's first child is the trigger (receives the current open state). The title slot (or title prop) renders an optional heading, and the content slot (receives a close callback) renders the body.

<Popover title="Title" arrow>
  <Button>Trigger</Button>
  <template #content="{ close }">
    Content
  </template>
</Popover>

ui keys: base, dialog, heading, arrow.

Examples

Placement

Click buttons
<script setup lang="ts">import { Button, Popover } from '@hareui/vue'</script><template>  <div class="grid grid-cols-3 gap-4">    <div />    <Popover side="top" arrow>      <Button class="w-full" variant="tertiary">        Top      </Button>      <template #content>        <p class="text-sm">Top placement</p>      </template>    </Popover>    <div />    <Popover side="left" arrow>      <Button class="w-full" variant="tertiary">        Left      </Button>      <template #content>        <p class="text-sm">Left placement</p>      </template>    </Popover>    <div class="flex items-center justify-center">      <span class="text-sm text-muted">Click buttons</span>    </div>    <Popover side="right" arrow>      <Button class="w-full" variant="tertiary">        Right      </Button>      <template #content>        <p class="text-sm">Right placement</p>      </template>    </Popover>    <div />    <Popover side="bottom" arrow>      <Button class="w-full" variant="tertiary">        Bottom      </Button>      <template #content>        <p class="text-sm">Bottom placement</p>      </template>    </Popover>    <div />  </div></template>

With Arrow

<script setup lang="ts">import { Icon } from '@iconify/vue'import { Button, Popover } from '@hareui/vue'</script><template>  <div class="flex items-center gap-4">    <Popover title="Popover with Arrow" arrow class="max-w-64">      <Button variant="secondary">With Arrow</Button>      <template #content>        <p class="mt-2 text-sm text-muted">          The arrow shows which element triggered the popover.        </p>      </template>    </Popover>    <Popover title="Popover with Arrow" arrow class="max-w-64" :side-offset="10">      <Button icon-only aria-label="More options" variant="tertiary">        <Icon icon="gravity-ui:ellipsis" />      </Button>      <template #content>        <p class="mt-2 text-sm text-muted">          The arrow shows which element triggered the popover.        </p>      </template>    </Popover>  </div></template>

Interactive Content

<script setup lang="ts">import { ref } from 'vue'import { Avatar, Button, Popover } from '@hareui/vue'const isFollowing = ref(false)</script><template>  <div class="flex items-center gap-6">    <Popover class="w-[320px]">      <template #default="{ open }">        <button type="button" class="flex items-center gap-2" :aria-expanded="open" aria-label="User profile">          <Avatar size="sm" alt="Sarah Johnson" src="https://img.heroui.chat/image/avatar?w=400&h=400&u=1" fallback="SJ" />          <div class="flex flex-col items-start">            <p class="text-sm font-medium">              Sarah Johnson            </p>            <p class="text-xs text-muted">              @sarahj            </p>          </div>        </button>      </template>      <template #title>        <div class="flex items-center justify-between">          <div class="flex items-center gap-3">            <Avatar size="md" alt="Sarah Johnson" src="https://img.heroui.chat/image/avatar?w=400&h=400&u=1" fallback="SJ" />            <div>              <p class="font-semibold">                Sarah Johnson              </p>              <p class="text-sm text-muted">                @sarahj              </p>            </div>          </div>          <Button            class="rounded-full"            size="sm"            :variant="isFollowing ? 'tertiary' : 'primary'"            @click="isFollowing = !isFollowing"          >            {{ isFollowing ? 'Following' : 'Follow' }}          </Button>        </div>      </template>      <template #content>        <p class="mt-3 text-sm text-muted">          Product designer and creative director. Building beautiful experiences that matter.        </p>        <div class="mt-3 flex gap-4">          <div>            <span class="font-semibold">892</span>            <span class="ms-1 text-sm text-muted">Following</span>          </div>          <div>            <span class="font-semibold">12.5K</span>            <span class="ms-1 text-sm text-muted">Followers</span>          </div>        </div>      </template>    </Popover>  </div></template>

Customization

Tailwind CSS

<script setup lang="ts">import { Button, Popover } from '@hareui/vue'</script><template>  <Popover    :ui="{      base: 'max-w-56 overflow-hidden rounded-xl border border-border/80 bg-surface/90 p-0 shadow-xl ring-1 ring-black/5 backdrop-blur-xl dark:border-border/90 dark:bg-surface/85 dark:ring-white/10',      dialog: 'relative p-4',    }"  >    <Button variant="secondary">Details</Button>    <template #content>      <div        aria-hidden="true"        class="pointer-events-none absolute inset-x-0 top-0 h-12 bg-linear-to-b from-neutral-500/6 to-transparent dark:from-neutral-400/8"      />      <h3 data-slot="popover-heading" class="relative font-medium text-neutral-800 dark:text-neutral-100">        Keyboard shortcuts      </h3>      <dl class="relative mt-3 space-y-2 text-sm">        <div class="flex justify-between gap-4">          <dt class="text-muted">            Save          </dt>          <dd class="font-mono text-neutral-700 dark:text-neutral-300">            ⌘ S          </dd>        </div>        <div class="flex justify-between gap-4">          <dt class="text-muted">            Search          </dt>          <dd class="font-mono text-neutral-700 dark:text-neutral-300">            ⌘ K          </dd>        </div>      </dl>    </template>  </Popover></template>

Global theme override

app.use(createHareUI({
  ui: {
    popover: {
      slots: { base: 'rounded-xl' },
    },
  },
}))

Styling Reference

ui keydata-slotDescription
basepopoverThe floating surface
dialogpopover-dialogThe dialog wrapper around the heading/content
headingpopover-headingThe heading (shown when title prop or title slot is set)
arrowpopover-overlay-arrowThe arrow <svg> (shown when arrow is true)

Interactive states

  • Open/closed: data-state="open" | "closed" (drives the enter/exit animation)
  • Side: data-side="top" | "right" | "bottom" | "left"

API Reference

Props

PropTypeDefaultDescription
openbooleanundefinedThe controlled open state of the popover.
defaultOpenbooleanfalseThe open state of the popover when it is initially rendered. Use when you do not need to control its open state.
titlestring-The title rendered inside the popover's heading.
side"top" | "bottom" | "right" | "left""bottom"The preferred side of the trigger to render against when open.
align"center" | "start" | "end""center"The preferred alignment against the trigger.
sideOffsetnumber8The distance in pixels from the trigger.
flipbooleantrueWhether the popover can change orientation to fit in the viewport when it would otherwise overflow.
arrowbooleanfalseWhether to render an arrow pointing at the trigger.
dismissiblebooleantrueWhen false, the popover will not close when clicking outside or pressing Escape.
portalstring | boolean | HTMLElementtrueRenders the popover content in a portal. true portals to document.body, a string is used as the teleport target, and false disables portalling.
uiComponentSlots<{ slots: { base: string[]; dialog: string; heading: string; arrow: string; }; }>-Per-slot class overrides.

Slots

SlotPropsDescription
default{ open: boolean; }The trigger element. Receives the current open state.
titleanyReplaces the heading text derived from title.
content{ close: () => void; }The popover's content. Receives a close callback.

On this page

No Headings