Vize

on-click-outside

Call a handler when the user clicks (or focuses an iframe) outside an element.

Package @vizejs/composable/on-click-outside
Own the source vize lib pull composable:on-click-outside
Runtime exports onClickOutside
Gzip budget 2816 B

Minimal setup

Use a Vue 3.5+ project with Vue SFC compilation configured (see Vite+ integration).

vp install @vizejs/composable

Import the entry directly. Call lifecycle-bound utilities inside <script setup> so cleanup follows the component scope; consult the runtime contract for this entry's SSR and hydration behavior.

import { onClickOutside } from "@vizejs/composable/on-click-outside";

Try it

Dismiss inline guide preferences

Keep an expandable preference region open during inside and ignored clicks, then dismiss it when an actual outside click occurs.

Open the interactive example

What to observe: Open the preferences and edit Review note. Apply local preference leaves the region open and reports the note. Keep preferences open is outside but ignored. Continue reading outside dismisses the region and increments the counter. Reopen it and press Escape inside: the example closes it, returns focus to its trigger, and leaves the outside-dismissal count unchanged.

The target is a template ref recreated by v-if; the trigger and separate keep-open button are ignored element targets. The composable tracks pointerdown as well as click, so a drag starting inside and ending outside does not dismiss it. Native keyboard activation of an outside button also emits a click. This is an inline non-modal region with ordinary Tab order; Escape and trigger focus are explicit example behavior, not features supplied by onClickOutside. The default window host is absent during SSR and scope disposal removes the client listeners. Applying a note changes only local display state.

Compare the initial and interacted states
Dismiss inline guide preferences: initial state
Initial state
Dismiss inline guide preferences: state after the verified interactions
After the verified interactions

Copy the example

Save this complete SFC in your Vue project. Its public imports and source are the same ones executed in the preview.

<script setup lang="ts">
import { ref, useId, useTemplateRef } from "vue";
import { onClickOutside } from "@vizejs/composable/on-click-outside";

const preferencesId = useId();
const panel = useTemplateRef<HTMLElement>("panel");
const trigger = useTemplateRef<HTMLButtonElement>("trigger");
const ignored = useTemplateRef<HTMLButtonElement>("ignored");
const open = ref(false);
const note = ref("Keep examples copyable.");
const dismissals = ref(0);
const message = ref("No dismissal yet.");

onClickOutside(
  panel,
  () => {
    if (!open.value) return;
    open.value = false;
    dismissals.value += 1;
    message.value = "Dismissed by an outside click.";
  },
  { ignore: [trigger, ignored] },
);

function closeWithEscape(): void {
  open.value = false;
  message.value = "Closed with Escape inside the preferences.";
  trigger.value?.focus();
}
</script>

<template>
  <div class="composable-example">
    <button
      ref="trigger"
      type="button"
      :aria-expanded="open"
      :aria-controls="`${preferencesId}-panel`"
      @click="() => (open = !open)"
    >
      {{ open ? "Close guide preferences" : "Open guide preferences" }}
    </button>
    <section
      v-if="open"
      :id="`${preferencesId}-panel`"
      ref="panel"
      :aria-labelledby="`${preferencesId}-title`"
    >
      <h2 :id="`${preferencesId}-title`">Guide preferences</h2>
      <label :for="`${preferencesId}-note`">Review note</label>
      <input :id="`${preferencesId}-note`" v-model="note" @keydown.esc="closeWithEscape" />
      <button
        type="button"
        @click="() => (message = `Local note: ${note}`)"
        @keydown.esc="closeWithEscape"
      >
        Apply local preference
      </button>
      <p>
        This inline region keeps ordinary Tab navigation. Escape closes it and focuses its trigger.
      </p>
    </section>
    <div class="example-actions">
      <button ref="ignored" type="button">Keep preferences open</button>
      <button type="button">Continue reading outside</button>
    </div>
    <output aria-live="polite">Outside dismissals: {{ dismissals }} · {{ message }}</output>
    <p>The trigger and Keep preferences open are ignored. Clicking inside preserves the region.</p>
  </div>
</template>

Runtime contract

Utility Category Stability SSR Hydration Cleanup Targets Host globals Uses
onClickOutside events experimental deterministic-fallback stable caller, reactive-scope web, desktop globalThis, window resolveElement, tryOnScopeDispose

API

onClickOutside

Call a handler when the user clicks (or focuses an iframe) outside an element. A click counts as outside only when its pointerdown also started outside the target, so drags that begin inside (e.g. text selection) and end outside are ignored. Event paths (composedPath()) make shadow-DOM targets work. The host is resolved once, when called. Nothing is registered during server rendering; listeners are removed with the owning scope or by the returned stop function.

function onClickOutside( target: MaybeElementTarget, handler: (event: Event) => void, options: OnClickOutsideOptions = {}, ): () => void

Types

ClickOutsideHost

Window-like capability observed by onClickOutside.

Member Type Description
document { readonly activeElement: Element | null } Document used to detect focus moving into an iframe.

OnClickOutsideOptions

Options for onClickOutside.

Member Type Description
ignore? MaybeRefOrGetter<readonly ClickOutsideIgnore[]> Elements (or selectors) whose clicks never count as outside, e.g. the button that toggles a popover.
capture? boolean Listen in the capture phase so stopPropagation() inside the page cannot hide outside clicks.
detectIframe? boolean Treat focus moving into an <iframe> outside the target as an outside click (iframes swallow pointer events).
scheduler? TimeoutScheduler Owns the zero-delay timer used for iframe detection.
host? MaybeRefOrGetter<ClickOutsideHost | null | undefined> Reactive window capability for alternate runtimes and tests.