Interactions

useEscapeKey

Closes floating content when the user presses the Escape key.

useEscapeKey closes a floating surface when the user presses the Escape key. It coordinates through the composite floating node hierarchy to unwind nested trees leaf-first, resolves independent overlay stacks in LIFO order, and protects against accidental closure during IME text composition.

Type

function useEscapeKey(node: FloatingNode, options?: UseEscapeKeyOptions): void;

type UseEscapeKeyContext = FloatingNode;

interface UseEscapeKeyOptions {
  enabled?: MaybeRefOrGetter<boolean>;
  capture?: boolean;
  preventDefault?: boolean;
  onEscape?: (event: KeyboardEvent) => void;
}

Options

NameTypeDefaultNotes
enabledMaybeRefOrGetter<boolean>trueReactive toggle. Setting to false removes the node from the escape stack and ignores keystrokes.
capturebooleanfalseStatic setup option. Changing it mid-open takes effect on close/re-open. Attaches keydown listener during the capture phase.
preventDefaultbooleanfalseCalls event.preventDefault() on handled Escape presses.
onEscape(event: KeyboardEvent) => voidundefinedCustom callback. When provided, replaces the default node.open.value = false.

Returns

useEscapeKey returns void. When an Escape key press targets this surface, it updates node.open.value = false (or invokes options.onEscape if provided).

Details

Leaf-First Escape Unwinding

When using nested floating nodes (such as multi-level cascading menus):

  • Hierarchical Depth Order: When Escape is pressed, parent nodes inspect their open children. Only the deepest active descendant closes.
  • Step-by-Step Dismissal: Consecutive Escape presses pop each open level one by one back up to the root, providing standard operating system menu navigation semantics without custom coordination code.

Stacked Independent Overlays (LIFO)

When multiple independent overlays coexist on the page (for example, a modal dialog that opens a select dropdown):

  • Focused Surface First: If focus resides inside one of the open overlays, that overlay claims the Escape key.
  • LIFO Order: If focus is neutral (e.g. on the document body), the most recently opened surface closes first. Unrelated overlays do not intercept each other's Escape presses.
  • Shadow DOM Target Resolution: The event source is resolved via composedPath() when available, so Escape pressed inside a shadow root is attributed to the overlay whose family actually contains the inner element. Falls back to event.target for synthetic or legacy events.

IME Composition Awareness

When users type using an Input Method Editor (IME) for languages like Japanese, Chinese, or Korean, pressing Escape cancels the pending composition candidate window.

useEscapeKey listens to compositionstart and compositionend events at the document level. Any Escape keystroke received while isComposing is active is ignored, preventing the floating surface from closing prematurely while candidate selection is being aborted.

Example

<script setup lang="ts">
import { ref } from "vue";
import { useClick, useEscapeKey, useFloatingNode, 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,
  },
});

useClick(node);
useEscapeKey(node, {
  preventDefault: true,
});
</script>

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

  <div v-if="node.open.value" ref="floatingEl" class="menu">
    <p>Press Escape to dismiss this menu.</p>
  </div>
</template>

See Also

Released under the MIT License. Copyright © 2026