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
| Name | Type | Default | Notes |
|---|---|---|---|
enabled | MaybeRefOrGetter<boolean> | true | Reactive toggle. Setting to false disables outside click detection. |
capture | boolean | true | Static setup option. Changing it mid-open takes effect on close/re-open. Selects the document listener phase. |
ignoreScrollbar | boolean | true | Static flag. Pointer interactions on scrollbar gutters do not trigger dismissal. |
shouldIgnore | OutsideClickPredicate | undefined | Custom predicate to ignore specific outside interactions. Evaluated after the composite node family check. |
onOutsideClick | (event: MouseEvent | PointerEvent, info: OutsideClickInfo) => void | undefined | Custom 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 onpointerdown($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 withpointercancel, keeping the overlay open. - iOS Safari / WebKit Compatibility: When a user taps to dismiss, the gesture completes with
pointerup. Unlike syntheticclickevents (which iOS Safari suppresses on non-interactive elements like plain<div>containers,<body>, or blank background whitespace), standard W3Cpointerupevents 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.
- Scroll-Safe: On initial touch contact (
- Keyboard & Screen Readers (
click): Virtual activations from keyboard keys (Enter/Space) or assistive technology (VoiceOver, TalkBack) fire syntheticclickevents withdetail === 0. These are detected and handled onclick. 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.useOutsideClicklistens for windowblurevents and verifies whetherdocument.activeElementtransitioned 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
useEscapeKey- Close on Escape key press with leaf-first tree coordinationuseClick- Toggle open on click or keyboard activationuseFloatingNode- Composite node with parent-child coordination- Build Popovers & Dropdowns - Popover interaction patterns