Skip to content

useDismiss

useDismiss closes a floating node when the user presses Escape or clicks outside the surface. Both channels share one reactive enabled gate and coordinate through the unified composite floating node.

Type

ts
function useDismiss(node: FloatingNode, options?: UseDismissOptions): void;

interface UseDismissOptions {
  enabled?: MaybeRefOrGetter<boolean>;
  escapeKey?: boolean | UseDismissEscapeOptions;
  outsidePress?: boolean | UseDismissOutsideOptions;
}

interface UseDismissEscapeOptions {
  capture?: boolean;
  preventDefault?: boolean;
  onEscape?: (event: KeyboardEvent) => void;
  ignoreEscapeKey?: (event: KeyboardEvent) => boolean;
}

interface UseDismissOutsideOptions {
  event?: MaybeRefOrGetter<"pointerdown" | "mousedown" | "click">;
  capture?: MaybeRefOrGetter<boolean>;
  ignoreClick?: (event: MouseEvent, target: EventTarget | null) => boolean;
  onClick?: (event: MouseEvent) => void;
  ignoreScrollbar?: MaybeRefOrGetter<boolean>;
  ignoreDrag?: MaybeRefOrGetter<boolean>;
}

Options

NameTypeDefaultNotes
enabledMaybeRefOrGetter<boolean>trueShared reactive gate for Escape and outside press channels.
escapeKeyboolean | UseDismissEscapeOptionstruefalse disables Escape dismissal; an object configures it.
outsidePressboolean | UseDismissOutsideOptionstruefalse disables outside-press dismissal; an object configures it.

Escape Channel Options (escapeKey object)

OptionTypeDefaultNotes
capturebooleanfalseAttaches keydown listener during the capture phase. Read once.
preventDefaultbooleanfalseCalls event.preventDefault() on handled Escape presses.
onEscape(event: KeyboardEvent) => voidundefinedCustom handler. Replaces default node.open.value = false.
ignoreEscapeKey(event: KeyboardEvent) => booleanundefinedPredicate to let children or custom inputs consume Escape first.

Outside Press Channel Options (outsidePress object)

OptionTypeDefaultNotes
eventMaybeRefOrGetter<"pointerdown" | "mousedown" | "click">"pointerdown"Which document event triggers dismissal.
captureMaybeRefOrGetter<boolean>trueRuns during listener capture phase before bubbling completes.
ignoreClick(event, target) => booleanundefinedSkips selected clicks; runs after the composite node family check.
onClick(event: MouseEvent) => voidundefinedCustom handler. Replaces default node.open.value = false.
ignoreScrollbarMaybeRefOrGetter<boolean>trueClicking scrollbars inside the panel does not trigger dismissal.
ignoreDragMaybeRefOrGetter<boolean>trueFor event: "click", ignores mouseup outside after dragging from inside.

Returns

useDismiss returns void. When dismissed, it updates node.open.value = false.

Details

Hierarchical Outside Clicks & Leaf-First Escape

useDismiss natively leverages the composite floating node hierarchy:

  • Family-Aware Outside Click: 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 closures.
  • Leaf-First Escape Protocol: When Escape is pressed in a nested cascade (e.g. Root → Submenu → SubSubmenu), parent nodes inspect their open children. If open children exist, parent nodes pass through execution so only the deepest leaf node closes. Subsequent Escape presses pop each ancestor in reverse depth order.

Outside Press Detection

  • By default, outside presses trigger on "pointerdown" in the capture phase. This dismisses the surface before any blur or pointerup handlers run on other page elements.
  • ignoreScrollbar: true prevents dismissal when users drag scrollbars on overflowing panels.
  • ignoreDrag: true prevents closing when a user selects text inside the panel and releases the mouse cursor outside.

Example

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

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

const node = useFloatingNode({ anchorEl, floatingEl });
usePosition(node);

useClick(node);
useDismiss(node, {
  outsidePress: { event: "pointerdown" },
  escapeKey: { preventDefault: true },
});
</script>

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

  <div v-if="node.open.value" ref="floatingEl" class="popover">
    <p>Press Escape or click outside to dismiss</p>
  </div>
</template>

See Also