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 |
Usage
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).
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.