Interactions

useOutsideClick

Closes floating content when pointer input lands outside its floating family.

useOutsideClick closes a floating node when pointer input lands outside its floating surface. It natively leverages the composite floating node hierarchy, treating child submenus and nested overlays as internal to prevent premature closure.

Type

function useOutsideClick(node: FloatingNode, options?: UseOutsideClickOptions): void;

interface UseOutsideClickOptions {
  enabled?: MaybeRefOrGetter<boolean>;
  capture?: boolean;
  ignoreScrollbar?: boolean;
  shouldIgnore?: OutsideClickPredicate;
  onOutsideClick?: (event: MouseEvent | PointerEvent, info: OutsideClickInfo) => void;
}

interface OutsideClickInfo {
  reason: "pointer" | "iframe-blur";
  target: EventTarget | null;
}

type OutsideClickPredicate = (
  event: MouseEvent | PointerEvent,
  target: EventTarget | null,
  info: OutsideClickInfo,
) => boolean;

Options

NameTypeDefaultNotes
enabledMaybeRefOrGetter<boolean>trueReactive toggle. Setting to false disables outside click detection.
capturebooleantrueStatic setup option. Changing it mid-open takes effect on close/re-open. Selects the document listener phase.
ignoreScrollbarbooleantrueStatic flag. Pointer interactions on scrollbar gutters do not trigger dismissal.
shouldIgnoreOutsideClickPredicateundefinedCustom predicate to ignore specific outside interactions. Evaluated after the composite node family check.
onOutsideClick(event: MouseEvent | PointerEvent, info: OutsideClickInfo) => voidundefinedCustom callback. When provided, replaces default node.open.value = false.

Returns

useOutsideClick returns void. When an outside press is confirmed, it updates node.open.value = false (or invokes options.onOutsideClick if provided).

Details

Unified Input Model & Pointer Lifecycle

useOutsideClick automatically adapts to desktop mouse, touchscreen, and assistive technologies without manual event configuration:

  • Desktop Mouse & Pen (pointerdown): Handled eagerly on pointerdown ($t = 0$) in the capture phase. This gives desktop users instant feedback without waiting for release clicks, while automatically suppressing text selection drag-outs (selecting text inside the overlay and releasing outside cannot dismiss the overlay).
  • Mobile Touchscreens (pointerup & pointercancel):
    • Scroll-Safe: On initial touch contact (pointerdown), dismissal is deferred. If the user touches the screen to scroll, pan, or zoom, the browser cancels the touch stream with pointercancel, keeping the overlay open.
    • iOS Safari / WebKit Compatibility: When a user taps to dismiss, the gesture completes with pointerup. Unlike synthetic click events (which iOS Safari suppresses on non-interactive elements like plain <div> containers, <body>, or blank background whitespace), standard W3C pointerup events fire across all DOM nodes, ensuring reliable dismissal on mobile Safari.
    • Drag-Out Protection: Touch interactions that begin inside the floating surface and drag outward are ignored on release because the initial touch-down was internal.
  • Keyboard & Screen Readers (click): Virtual activations from keyboard keys (Enter / Space) or assistive technology (VoiceOver, TalkBack) fire synthetic click events with detail === 0. These are detected and handled on click. Physical trailing clicks (detail > 0) are ignored to avoid duplicate callback invocations.

Family-Aware Hierarchy Traversal

useOutsideClick works seamlessly with nested menus and popovers:

  • Composite Node Containment: node.contains(target) traverses open child surfaces. Clicking inside a child submenu (even if teleported to <body>) is recognized as internal to parent menus, preventing unwanted parent closures.
  • Clean-Slate Dismissal: Clicking the outside background closes all open levels simultaneously, matching universal user expectations for contextual menus and dropdowns.
  • Independent Stacks: Unrelated floating nodes on the same page remain isolated. Clicking outside a popover closes only the relevant surface while respecting neighboring overlays.
  • Shadow DOM Target Resolution: The press source is resolved via composedPath() when available, so clicks inside shadow roots are correctly attributed to the containing surface.

Scrollbar Hit-Testing

  • Scrollbar Protection: When ignoreScrollbar: true (default), clicks inside vertical or horizontal scrollbar gutters on overflowing parent elements or the viewport are ignored, allowing users to scroll without closing the overlay.
  • RTL-Aware: Correctly detects left-hand scrollbar gutters on right-to-left documents.

Iframe Focus Detection

  • When focus moves directly into an external <iframe> outside the floating family, mouse and pointer events are swallowed by the frame. useOutsideClick listens for window blur events and verifies whether document.activeElement transitioned into an external iframe, automatically closing the overlay.

Example

<script setup lang="ts">
import { ref } from "vue";
import { useClick, useFloatingNode, useOutsideClick, usePosition } from "v-float";

const anchorEl = ref<HTMLElement | null>(null);
const floatingEl = ref<HTMLElement | null>(null);

const node = useFloatingNode({ anchorEl, floatingEl });
usePosition(node, {
  placement: "bottom-start",
  middlewares: {
    offset: 8,
    flip: true,
    shift: { padding: 8 },
  },
});

useClick(node);
useOutsideClick(node);
</script>

<template>
  <button ref="anchorEl" type="button">Toggle Popover</button>

  <div v-if="node.open.value" ref="floatingEl" class="popover">
    <p>Click outside this panel to close it.</p>
  </div>
</template>

See Also

Released under the MIT License. Copyright © 2026