useHover
useHover opens and closes a floating node when the pointer enters or leaves the anchor or floating element. It supports open/close delays, rest-before-open timers, and safe polygons that bridge the gap between trigger and floating panel.
Type
function useHover(node: FloatingNode, options?: UseHoverOptions): void;
interface UseHoverOptions {
enabled?: MaybeRefOrGetter<boolean>;
delay?: MaybeRefOrGetter<number | { open?: number; close?: number }>;
restMs?: MaybeRefOrGetter<number>;
mouseOnly?: MaybeRefOrGetter<boolean>;
safePolygon?: MaybeRefOrGetter<boolean | SafePolygonOptions>;
ignorePointerLeave?: (target: EventTarget | null) => boolean;
}
interface SafePolygonOptions {
buffer?: number;
requireIntent?: boolean;
intentTimeout?: number;
blockPointerEvents?: boolean;
getScope?: () => HTMLElement | null;
onPolygonChange?: (polygon: Polygon) => void;
}
Options
| Name | Type | Default | Notes |
|---|---|---|---|
enabled | MaybeRefOrGetter<boolean> | true | Reactive toggle. Gates all hover listeners. |
delay | MaybeRefOrGetter<number | { open?: number; close?: number }> | 0 | Debounce duration in milliseconds for open and close transitions. |
restMs | MaybeRefOrGetter<number> | 0 | Duration the pointer must rest stationary over the anchor before opening. |
mouseOnly | MaybeRefOrGetter<boolean> | false | When true, ignores touch or pen hover events. |
safePolygon | MaybeRefOrGetter<boolean | SafePolygonOptions> | false | Keeps panel open while the pointer travels across the gap between anchor and floating panel. |
ignorePointerLeave | (target: EventTarget | null) => boolean | undefined | Callback returning true to ignore selected pointer-leave events. |
Safe Polygon Options
When safePolygon is true or an object, an invisible directional polygon is calculated between the pointer and the floating panel:
| Option | Type | Default | Notes |
|---|---|---|---|
buffer | number | 0.5 | Extra pixel padding added around the travel corridor. |
requireIntent | boolean | true | Requires pointer velocity and direction to point toward the floating element. |
intentTimeout | number | 40 | Delay in milliseconds before closing when pointer velocity drops below 0.1 px/ms. |
blockPointerEvents | boolean | false | Prevents background elements from firing pointer/hover events while traversing the safe corridor. |
getScope | () => HTMLElement | null | () => document.body | Resolves the root element subtree to shield when blockPointerEvents is enabled. |
onPolygonChange | (polygon: Polygon) => void | undefined | Callback receiving updated polygon coordinates for debugging or visualization. |
Returns
useHover returns void. It manages listeners on the anchor and floating elements that automatically clean up when components unmount.
Details
Preventing Accidental Triggers with restMs
When users quickly skim across a row of buttons or table cells, instant tooltips create visual noise. Setting restMs: 150 waits until the pointer rests stationary within the anchor element (moves $\le$ 4px) before opening.
To ensure accessibility for users with motor tremors or continuous scanning habits, useHover enforces an automatic 1000ms fallback ceiling delay (or Math.max(delay.open, 1000) if delay.open is larger). If the pointer remains inside the anchor for the duration of the fallback delay, the floating element opens even if the cursor moves continuously.
Bridging Gaps with safePolygon
If your floating panel is separated from the anchor by an offset margin, moving the mouse to click an item in the panel would trigger a pointerleave event on the anchor and dismiss the panel.
Enabling safePolygon: true tracks the pointer trajectory. As long as the cursor moves toward the floating panel inside the dynamic cone, the surface remains open.
Preventing Background Flicker with blockPointerEvents and getScope
In dense navigation bars, multi-column flyouts, and mega-menus, moving the cursor diagonally across the screen to reach a submenu often passes over sibling links or neighboring navigation items. Even with safePolygon active, those underlying DOM elements still fire pointer events by default, causing unwanted hover states, button highlights, or secondary popups to flicker into view.
Setting blockPointerEvents: true in your safePolygon configuration temporarily sets pointer-events: none on the scope while keeping pointer-events: auto explicitly enabled on the anchor and floating elements. Reference-counted hold tracking ensures that nested menus and overlapping corridors never leave pointer-events disabled.
By default, the shielded scope is the anchor's document.body. To restrict shielding to a specific container (such as a sidebar navigation list or menu dropdown) so the rest of the application remains fully interactive during corridor traversal, pass a getScope function:
useHover(node, {
safePolygon: {
blockPointerEvents: true,
getScope: () => navMenuEl.value,
},
});
Tuning Intent Sensitivity with intentTimeout
When requireIntent is enabled (the default), useHover measures pointer velocity. If the cursor slows down or stops inside the safe corridor (dropping below 0.1 px/ms), useHover starts a timer before closing the floating panel.
The default intentTimeout is 40ms. If you build large mega-menus, multi-column navigation layouts, or designs where users pause briefly while scanning items mid-transit, increase intentTimeout (for example, 80 to 150) to provide a more forgiving grace period.
Spatial Family Awareness
When moving the pointer into a child submenu or nested floating surface, useHover verifies whether the pointer's destination (e.relatedTarget) is contained in the node family via node.contains(e.relatedTarget). Parent surfaces stay open without extra manual listener wiring.
Example
<script setup lang="ts">
import { ref } from "vue";
import { useFloatingNode, useHover, usePosition } from "v-float";
const anchorEl = ref<HTMLElement | null>(null);
const floatingEl = ref<HTMLElement | null>(null);
const node = useFloatingNode({ anchorEl, floatingEl });
usePosition(node, {
placement: "top",
middlewares: {
offset: 8,
flip: true,
shift: { padding: 8 },
},
});
useHover(node, {
delay: { open: 150, close: 100 },
safePolygon: true,
});
</script>
<template>
<button ref="anchorEl">Hover for details</button>
<div v-if="node.open.value" ref="floatingEl" class="card">
<p>Interactive floating card with links</p>
<a href="#more">Read documentation</a>
</div>
</template>
See Also
useClick- Toggle open state on click, tap, or keyboard activationuseFocus- Keyboard focus trigger for accessible tooltipsuseFloatingNode- Composite node with parent-child coordination- Build Accessible Tooltips - Tooltip patterns and best practices
- Safe Polygon Gotchas - Deep dive on polygon mathematics