Frappe UIFrappe UI

HoverCard

Shows a floating panel when the pointer rests on (or the keyboard focuses) a trigger — for profile previews, link previews, and other sighted-only context. Built on reka-ui's HoverCard primitives.

Use HoverCard for hover-driven panels and Popover for click-driven ones. For short text hints prefer Tooltip instead.

Example

#trigger is rendered as-child, so hover and focus a11y are wired automatically. #default renders inside the standard panel shell.

vue
<script setup lang="ts">
import { Avatar, HoverCard } from 'frappe-ui'
</script>

<template>
  <HoverCard>
    <template #trigger>
      <a href="#" class="text-ink-gray-9 underline underline-offset-2">
        @jane
      </a>
    </template>
    <template #default>
      <div class="flex w-64 gap-3 p-3">
        <Avatar
          label="Jane Doe"
          image="https://avatars.githubusercontent.com/u/499550?v=4"
          size="2xl"
        />
        <div>
          <p class="text-p-base font-medium text-ink-gray-9">Jane Doe</p>
          <p class="mt-1 text-p-sm text-ink-gray-6">
            Designer. Likes hover cards.
          </p>
        </div>
      </div>
    </template>
  </HoverCard>
</template>

Delays

hoverDelay and leaveDelay are in seconds (consistent with Tooltip). hoverDelay is how long the pointer must rest before the card opens; leaveDelay is how long after the pointer leaves before it closes.

vue
<script setup lang="ts">
import { Button, HoverCard } from 'frappe-ui'
</script>

<template>
  <HoverCard :hover-delay="0.1" :leave-delay="0.5">
    <template #trigger>
      <Button>Hover me</Button>
    </template>
    <template #default>
      <div class="p-3 text-p-sm text-ink-gray-7">
        Opens after 0.1s, closes 0.5s after the pointer leaves.
      </div>
    </template>
  </HoverCard>
</template>

Arrow

Set arrow to render a small arrow pointing at the trigger, styled to match the panel surface.

vue
<script setup lang="ts">
import { Avatar, HoverCard } from 'frappe-ui'
</script>

<template>
  <HoverCard side="top" arrow>
    <template #trigger>
      <a href="#" class="text-ink-gray-9 underline underline-offset-2">
        @jane
      </a>
    </template>
    <template #default>
      <div class="flex w-64 gap-3 p-3">
        <Avatar
          label="Jane Doe"
          image="https://avatars.githubusercontent.com/u/499550?v=4"
          size="2xl"
        />
        <div>
          <p class="text-p-base font-medium text-ink-gray-9">Jane Doe</p>
          <p class="mt-1 text-p-sm text-ink-gray-6">
            Designer. Likes hover cards.
          </p>
        </div>
      </div>
    </template>
  </HoverCard>
</template>

Styling

Like Popover, the card owns its panel shell and exposes the same stable data-slot hooks — [data-slot="content"] for the portaled content and [data-slot="content-body"] for the shell that owns the visuals. There are no class-injection props.

Motion

The card appears instantly. An 80ms fade smooths the paint on open, and there is no exit animation. prefers-reduced-motion is respected. No configuration is required.

Notes

  • HoverCard is for sighted-only enhancement — don't put essential actions or information that can only be reached by hovering.
  • The card stays open while the pointer is over either the trigger or the card, so users can move into it to interact.

API Reference

Show types
typescript
import { type HoverCardContentProps, type HoverCardPortalProps } from 'reka-ui'

export interface HoverCardProps {
  /**
   * Side of the trigger the card is placed on.
   */
  side?: HoverCardContentProps['side']

  /**
   * Alignment of the card relative to the trigger.
   */
  align?: HoverCardContentProps['align']

  /**
   * Distance in pixels between the card and the trigger.
   */
  offset?: number

  /**
   * Where the card is teleported to in the DOM. Unset, an embedding host's target is used, else `body`.
   */
  portalTo?: HoverCardPortalProps['to']

  /**
   * Padding (in pixels) kept between the card and the viewport edges when
   * repositioning to avoid collisions.
   */
  collisionPadding?: number

  /**
   * Delay (in seconds) from when the pointer enters the trigger until the card
   * opens. Matches the Tooltip convention of using seconds.
   */
  hoverDelay?: number

  /**
   * Delay (in seconds) from when the pointer leaves the trigger or card until
   * the card closes.
   */
  leaveDelay?: number

  /**
   * Render a small arrow pointing at the trigger. Styled to match the panel
   * surface.
   */
  arrow?: boolean
}

/** Slot props passed to the `#trigger` slot. */
export interface HoverCardSlotProps {
  /** Whether the card is currently open. */
  open: boolean
}

/** Methods available on a `<HoverCard>` template ref. */
export interface HoverCardExposed {
  /** Opens the card. */
  open: () => void
  /** Closes the card. */
  close: () => void
}
side
= "bottom"
"top" | "right" | "bottom" | "left"

Side of the trigger the card is placed on.

align
= "start"
"start" | "center" | "end"

Alignment of the card relative to the trigger.

offset
= 4
number

Distance in pixels between the card and the trigger.

portalTo
string | HTMLElement

Where the card is teleported to in the DOM. Unset, an embedding host's target is used, else `body`.

collisionPadding
= 10
number

Padding (in pixels) kept between the card and the viewport edges when repositioning to avoid collisions.

hoverDelay
= 0.3
number

Delay (in seconds) from when the pointer enters the trigger until the card opens. Matches the Tooltip convention of using seconds.

leaveDelay
= 0.3
number

Delay (in seconds) from when the pointer leaves the trigger or card until the card closes.

arrow
= false
boolean

Render a small arrow pointing at the trigger. Styled to match the panel surface.

open
= false
boolean
trigger
HoverCardSlotProps

Trigger element. Rendered as-child so hover/focus a11y is auto-wired.

default

Card contents, rendered inside the standard PopoverPanel shell.

update:open
[value: boolean]

Fired when the open state changes.