Interactions

useHover

Opens and closes floating content on pointer hover.

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

NameTypeDefaultNotes
enabledMaybeRefOrGetter<boolean>trueReactive toggle. Gates all hover listeners.
delayMaybeRefOrGetter<number | { open?: number; close?: number }>0Debounce duration in milliseconds for open and close transitions.
restMsMaybeRefOrGetter<number>0Duration the pointer must rest stationary over the anchor before opening.
mouseOnlyMaybeRefOrGetter<boolean>falseWhen true, ignores touch or pen hover events.
safePolygonMaybeRefOrGetter<boolean | SafePolygonOptions>falseKeeps panel open while the pointer travels across the gap between anchor and floating panel.
ignorePointerLeave(target: EventTarget | null) => booleanundefinedCallback 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:

OptionTypeDefaultNotes
buffernumber0.5Extra pixel padding added around the travel corridor.
requireIntentbooleantrueRequires pointer velocity and direction to point toward the floating element.
intentTimeoutnumber40Delay in milliseconds before closing when pointer velocity drops below 0.1 px/ms.
blockPointerEventsbooleanfalsePrevents background elements from firing pointer/hover events while traversing the safe corridor.
getScope() => HTMLElement | null() => document.bodyResolves the root element subtree to shield when blockPointerEvents is enabled.
onPolygonChange(polygon: Polygon) => voidundefinedCallback 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

Released under the MIT License. Copyright © 2026