Hover Card
Accessible, unstyled hover card for previewing linked content on hover or focus.
|
|
| Package |
@vizejs/ui/hover-card |
| Maturity |
stable |
| Own the source |
vize lib pull hover-card |
| Requires |
context, controllable-state, dismissable-layer, hover, id, pointer-grace, portal, positioner, presence, primitive |
| Aliases |
hover card, preview card, link preview, profile card |
| Covers |
Radix UI HoverCard, Reka UI HoverCard, Ark UI HoverCard |
Minimal setup
Use a Vue 3.5+ project with Vue SFC compilation configured (see Vite+ integration).
vp install @vizejs/ui
Add the public import below to <script setup lang="ts">. Components provide behavior without styles; the preview uses optional Paper styles.
import { HoverCard, HoverCardRoot, HoverCardContent, HoverCardTrigger, HoverCardArrow } from "@vizejs/ui/hover-card";
Or copy the source into your project with vize lib pull hover-card (see Source Distribution).
Try it
This is the basic example maintained with the component source, using the public package imports. The preview adds the optional Paper styles.
Open the interactive example · Styling these components
Copy the example
<!-- Profile preview that appears when a user link is hovered or focused. -->
<script setup lang="ts">
import { HoverCard, HoverCardArrow, HoverCardContent, HoverCardTrigger } from "@vizejs/ui/hover-card";
</script>
<template>
<p>
Reviewed by
<HoverCard :open-delay="300" :close-delay="150">
<HoverCardTrigger href="/users/ada">@ada</HoverCardTrigger>
<HoverCardContent placement="bottom">
<strong>Ada Lovelace</strong>
<span>Staff engineer on the compiler team.</span>
<HoverCardArrow />
</HoverCardContent>
</HoverCard>
on Monday.
</p>
</template>
API
HoverCard
Source: src/families/overlays/hover-card/hover-card-root.vue
Props
| Prop |
Type |
Default |
Description |
id |
string | null |
undefined |
Consumer-owned HoverCard base id. null and undefined select a deterministic fallback. |
open |
boolean |
undefined |
Controlled open state. undefined selects uncontrolled behavior. |
defaultOpen |
boolean |
false |
Initial open state for uncontrolled use. |
disabled |
boolean |
false |
Disable pointer, focus, and touch opening and close an open card. |
openDelay |
number |
700 |
Milliseconds the pointer or focus must rest on the trigger before opening. |
closeDelay |
number |
300 |
Milliseconds after the pointer or focus leaves before closing. Moving into the card or through the pointer-grace corridor cancels the close. |
touchBehavior |
HoverCardTouchBehavior |
"ignore" |
Touch handling on the trigger. Hover cards are supplementary, so touch is ignored by default and taps keep native link behavior. |
longPressDelay |
number |
500 |
Milliseconds a touch must be held when touchBehavior is "long-press". |
Events
| Event |
Payload |
Description |
update:open |
[value: boolean] |
Fired when the HoverCard requests a controlled open value. |
open-change |
[value: boolean, previous: boolean, nativeEvent: Event | null] |
Fired after any distinct open-state request. |
Slots
| Slot |
Slot props |
Description |
default |
HoverCardSlotState |
Compound HoverCard children. Receives the current open state. |
Exposed
| Member |
Type |
Description |
cancelPending |
— |
|
closeDelay |
— |
|
contentId |
— |
|
disabled |
— |
|
id |
— |
|
open |
— |
|
openDelay |
— |
|
reason |
— |
|
scheduleClose |
— |
|
scheduleOpen |
— |
|
setOpen |
— |
|
state |
— |
|
triggerId |
— |
|
HoverCardRoot
Source: src/families/overlays/hover-card/hover-card-root.vue
Props
| Prop |
Type |
Default |
Description |
id |
string | null |
undefined |
Consumer-owned HoverCard base id. null and undefined select a deterministic fallback. |
open |
boolean |
undefined |
Controlled open state. undefined selects uncontrolled behavior. |
defaultOpen |
boolean |
false |
Initial open state for uncontrolled use. |
disabled |
boolean |
false |
Disable pointer, focus, and touch opening and close an open card. |
openDelay |
number |
700 |
Milliseconds the pointer or focus must rest on the trigger before opening. |
closeDelay |
number |
300 |
Milliseconds after the pointer or focus leaves before closing. Moving into the card or through the pointer-grace corridor cancels the close. |
touchBehavior |
HoverCardTouchBehavior |
"ignore" |
Touch handling on the trigger. Hover cards are supplementary, so touch is ignored by default and taps keep native link behavior. |
longPressDelay |
number |
500 |
Milliseconds a touch must be held when touchBehavior is "long-press". |
Events
| Event |
Payload |
Description |
update:open |
[value: boolean] |
Fired when the HoverCard requests a controlled open value. |
open-change |
[value: boolean, previous: boolean, nativeEvent: Event | null] |
Fired after any distinct open-state request. |
Slots
| Slot |
Slot props |
Description |
default |
HoverCardSlotState |
Compound HoverCard children. Receives the current open state. |
Exposed
| Member |
Type |
Description |
open |
boolean |
Whether the card content is visible. |
disabled |
boolean |
Whether pointer, focus, and touch opening are disabled. |
state |
HoverCardState |
Stable state token for styling and tests. |
reason |
HoverCardOpenReason | null |
Why the card last opened, or null while closed. |
id |
string |
Root-owned base id. |
triggerId |
string |
Id wired to the trigger. |
contentId |
string |
Id wired to the content. |
openDelay |
number |
Milliseconds before pointer or focus intent opens the card. |
closeDelay |
number |
Milliseconds before pointer or focus departure closes the card. |
setOpen |
(value: boolean, event?: Event | null) => boolean |
Request a specific open value immediately and report whether it differs. |
scheduleOpen |
(event?: Event | null) => boolean |
Open after openDelay, cancelling a pending close. |
scheduleClose |
(event?: Event | null) => boolean |
Close after closeDelay, cancelling a pending open. |
cancelPending |
() => boolean |
Cancel any pending delayed open or close. Returns whether a timer was cleared. |
HoverCardContent
Source: src/families/overlays/hover-card/hover-card-content.vue
Props
| Prop |
Type |
Default |
Description |
forceMount |
boolean |
false |
Keep the card mounted while closed. |
to |
string | HTMLElement |
"body" |
CSS selector or element the card layer is moved into. |
portalDisabled |
boolean |
false |
Render in place instead of teleporting. |
defer |
boolean |
true |
Keep content in place until the target exists, avoiding SSR mismatch. |
placement |
Placement |
"bottom" |
Preferred placement before collision handling. |
strategy |
PositionerStrategy |
"fixed" |
CSS positioning mode published on the floating host. |
offset |
number |
8 |
Gap on the main axis between trigger and card. |
collisionPadding |
number |
8 |
Viewport padding the card should not cross. |
flip |
boolean |
true |
Flip to the opposite side when the preferred side overflows more. |
shift |
boolean |
true |
Shift the card back into the viewport after flip. |
hide |
boolean |
true |
Hide when the trigger no longer intersects the viewport. |
updateOnScroll |
boolean |
true |
Recalculate while ancestors scroll. |
updateOnResize |
boolean |
true |
Recalculate when the document or visual viewport resizes. |
viewport |
Rect |
undefined |
Viewport used for flip, shift, and hide. |
closeOnEscape |
boolean |
true |
Let Escape close the card. |
closeOnPointerDownOutside |
boolean |
true |
Let an outside pointer-down close the card. |
ariaLabel |
string |
undefined |
Accessible name for the card when its contents do not provide one. |
Events
| Event |
Payload |
Description |
escape-key-down |
[event: DismissableLayerEscapeKeyDownEvent] |
Fired before Escape requests dismissal. |
pointer-down-outside |
[event: DismissableLayerPointerDownOutsideEvent] |
Fired before an outside pointer-down requests dismissal. |
dismiss |
[event: DismissableLayerDismissEvent] |
Fired after an unprevented dismissal request. |
Slots
| Slot |
Slot props |
Description |
default |
HoverCardContentSlotState |
Card contents. Receives open state, open reason, and preferred placement. |
Exposed
| Member |
Type |
Description |
open |
boolean |
Whether the card content is visible. |
disabled |
boolean |
Whether pointer, focus, and touch opening are disabled. |
state |
HoverCardState |
Stable state token for styling and tests. |
reason |
HoverCardOpenReason | null |
Why the card last opened, or null while closed. |
element |
HTMLDivElement | null |
Rendered card element. |
HoverCardTrigger
Source: src/families/overlays/hover-card/hover-card-trigger.vue
Props
| Prop |
Type |
Default |
Description |
as |
PrimitiveAs |
"a" |
Element or component rendered as the trigger. Hover cards usually enhance a link, so the default is a native anchor; pass href and other link attributes as fallthrough attributes. |
disabled |
boolean |
false |
Ignore pointer, focus, and touch intent from this trigger. |
Slots
| Slot |
Slot props |
Description |
default |
HoverCardSlotState |
Trigger contents. Receives the current HoverCard state. |
Exposed
| Member |
Type |
Description |
element |
HTMLElement | null |
Rendered trigger element. |
focus |
(options?: FocusOptions) => void |
Move focus to the trigger. |
HoverCardArrow
Source: src/families/overlays/positioner/positioner-arrow.vue
Slots
| Slot |
Slot props |
Description |
default |
{ readonly x: number | null; readonly y: number | null } |
Decorative arrow contents. Receives coordinates for optional custom drawing. |
Exposed
| Member |
Type |
Description |
element |
Readonly<ShallowRef<HTMLElement | null>> |
Template ref to the rendered root element. |
x |
— |
|
y |
— |
|
Behavior
Normative behavior for the @vizejs/ui/hover-card compound primitive. A hover card
previews supplementary content for sighted pointer and keyboard users; the trigger keeps
its native link semantics and the card is never the only way to reach its content. Every
row is proven by the named mounted-DOM, SSR, or compile-time test.
| State x input |
Observable outcome |
Proven by |
closed, mouse or pen pointerenter on the trigger |
The card opens after openDelay (default 700 ms), never earlier; the trigger gains aria-describedby and content gets data-reason="hover". |
hover opens after openDelay with link semantics and describedby wiring |
open, pointerleave from the trigger |
The card closes after closeDelay (default 300 ms); re-entering the trigger first cancels the close. |
leaving closes after closeDelay unless the pointer returns or enters the card |
| open, pointer enters or leaves the card |
Entering the card cancels a pending close; leaving the card schedules one. |
leaving closes after closeDelay unless the pointer returns or enters the card |
| open, pointer travels from the trigger toward the card |
Pointer moves inside the pointer-grace safe triangle cancel the close; moves outside schedule it again. |
pointer grace keeps the card open while travelling toward it |
closed, trigger focus |
The card opens after openDelay with data-reason="focus". |
focus opens the card and blur closes it unless focus moves into the card |
open, trigger blur |
Focus moving into the card keeps it open; focus moving elsewhere schedules the close. |
focus opens the card and blur closes it unless focus moves into the card |
| open, Escape on the trigger or card; outside pointer-down |
The card closes immediately through the dismissable layer; Escape is consumed and dismiss reports the reason. |
Escape and outside pointer-down dismiss immediately |
touchBehavior="ignore", touch input |
Touch never opens the card and taps keep native link activation. |
touch is ignored by default and keeps native link activation |
touchBehavior="long-press", touch held |
Holding for longPressDelay opens the card (data-reason="long-press"), suppresses the context menu and the one follow-up click; moving past the touch slop cancels. |
long-press touch opens the card and suppresses the follow-up click |
disabled root |
Pointer, focus, and touch intent are ignored and an open card closes. |
disabled roots ignore intent and close an open card |
controlled open |
Intent emits update:open and open-change while rendered state follows the prop. |
controlled open follows the parent after emitting requests |
| root expose |
scheduleOpen, scheduleClose, cancelPending, setOpen, normalized delays, ids, reason, and state are available. |
root exposes delays and programmatic scheduling |
| parts outside the root |
Mounting throws VIZE_UI_CONTEXT_MISSING. |
trigger and content require a HoverCard root |
| SSR, closed |
Isolated requests render byte-identical span-only markup that is valid inside <p>, with no document listeners or timers. |
renders byte-identical closed hover-card markup that nests in phrasing content |
SSR, defaultOpen |
Content renders in place through the deferred portal and hydrates without warnings before teleporting. |
renders default-open content in place and hydrates without diagnostics |
| public types |
Touch behavior, open reason, delays, and placement are closed, typed contracts. |
src/families/overlays/hover-card/hover-card.types.test-d.ts |
| DOM/SSR/Vapor |
Root, trigger, and content compile in every renderer lane. |
scripts/check-renderers.ts |
Components
| Component |
State x input |
Outcome |
hover-card-root.vue |
controlled or uncontrolled open, timers |
Owns open state, open/close delays, open reason, and emits update:open/open-change. |
hover-card-trigger.vue |
hover, focus, blur, Escape, touch |
Schedules opening and closing, tracks the pointer-grace corridor, and handles long-press touch. |
hover-card-content.vue |
open, pointer enter/leave, focus, dismiss |
Portals and positions the card, keeps it open while hovered or focused, and dismisses via Escape or outside pointer-down. |
Public Root Props
| Prop |
Type |
Default |
Contract |
id |
string | null |
undefined |
Consumer-owned base id. |
open |
boolean |
undefined |
Controlled open state. |
defaultOpen |
boolean |
false |
Initial uncontrolled open state. |
disabled |
boolean |
false |
Ignore intent and close an open card. |
openDelay |
number |
700 |
Milliseconds before hover or focus opens. Invalid values mean 0. |
closeDelay |
number |
300 |
Milliseconds before departure closes. Invalid values mean 0. |
touchBehavior |
"ignore" | "long-press" |
"ignore" |
Touch policy. |
longPressDelay |
number |
500 |
Touch hold duration for "long-press". |
Parts And Data
| Target |
Public contract |
| Root |
<span>, part="root", data-vize-ui="hover-card-root", data-state, data-disabled |
| Trigger |
<a> by default (as), part="trigger", data-vize-ui="hover-card-trigger", data-state, data-disabled |
| Content host |
<span>, part="content-host", data-vize-ui="hover-card-content-host" |
| Content |
part="content", data-vize-ui="hover-card-content", data-state, data-placement, data-reason, data-top-layer |
| Arrow |
HoverCardArrow is the shared PositionerArrow and must render inside HoverCardContent. |
HoverCard ships no stylesheet. Consumers own all visual styling.