npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

vue3-click-outside-directive

v1.0.2

Published

Vue 3 directive that fires when a click is registered outside the element

Downloads

448

Readme

v-click-outside

Vue 3 directive that fires when a click is registered outside of an element.
TypeScript-first · Zero dependencies · Shadow DOM ready · SSR safe

npm version license


Installation

npm install vue3-click-outside-directive
# or
pnpm add vue3-click-outside-directive
# or
yarn add vue3-click-outside-directive

Vue 3

Global registration

Register the plugin once in main.ts — the directive becomes available in every component.

// main.ts
import { createApp } from "vue";
import { ClickOutsidePlugin } from "vue3-click-outside-directive";
import App from "./App.vue";

const app = createApp(App);
app.use(ClickOutsidePlugin);
app.mount("#app");
<template>
  <div v-click-outside="onClickOutside">...</div>
</template>

<script setup lang="ts">
function onClickOutside(event: PointerEvent) {
  console.log("clicked outside", event);
}
</script>

Local registration

Import the directive directly in the component — no global setup needed.

<script setup lang="ts">
import { vClickOutside } from "vue3-click-outside-directive";

function onClickOutside(event: PointerEvent) {
  console.log("clicked outside", event);
}
</script>

<template>
  <div v-click-outside="onClickOutside">...</div>
</template>

TypeScript — global directive types

When using global registration, add a type declaration so the template gets proper type checking:

// src/types/directives.d.ts
import type { Directive } from "vue";
import type { ClickOutsideHandler, ClickOutsideOptions } from "vue3-click-outside-directive";

declare module "vue" {
  interface GlobalDirectives {
    vClickOutside: Directive<
      HTMLElement,
      ClickOutsideHandler | ClickOutsideOptions
    >;
  }
}

Nuxt 3 / Nuxt 4

Plugin

Create a plugin file — Nuxt picks it up automatically, no manual registration needed.

// plugins/v-click-outside.ts
import { ClickOutsidePlugin } from "vue3-click-outside-directive";

export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.vueApp.use(ClickOutsidePlugin);
});

TypeScript — global directive types

// types/directives.d.ts
import type { Directive } from "vue";
import type { ClickOutsideHandler, ClickOutsideOptions } from "vue3-click-outside-directive";

declare module "vue" {
  interface GlobalDirectives {
    vClickOutside: Directive<
      HTMLElement,
      ClickOutsideHandler | ClickOutsideOptions
    >;
  }
}

SSR

The directive is SSR-safe — it registers the pointerdown listener only in mounted, which runs exclusively on the client.


API

Simple — pass a handler function

<div v-click-outside="handler" />
function handler(event: PointerEvent) {
  console.log("clicked outside", event);
}

Advanced — pass an options object

<div v-click-outside="{ handler, ignore: [triggerEl], disabled: isDisabled }" />

| Option | Type | Default | Description | | ---------- | ----------------------------------------- | ------- | ------------------------------------------------------ | | handler | (e: PointerEvent) => void | — | Required. Called when a click outside is detected | | ignore | Array<HTMLElement \| null \| undefined> | [] | Elements whose clicks are treated as "inside" | | disabled | boolean | false | Disable the directive without removing it from the DOM |


Examples

Dropdown menu

The most common use case. The ignore option prevents the trigger button from immediately closing the menu it just opened.

<script setup lang="ts">
import { ref } from "vue";

const open = ref(false);
const triggerRef = ref<HTMLElement>();

function close() {
  open.value = false;
}
</script>

<template>
  <button ref="triggerRef" @click="open = !open">Menu</button>

  <ul v-if="open" v-click-outside="{ handler: close, ignore: [triggerRef] }">
    <li>Item 1</li>
    <li>Item 2</li>
  </ul>
</template>

Why ignore: [triggerRef]?
Without it, clicking the button fires @click (opens the menu) and v-click-outside simultaneously (closes it), because the button is outside the <ul>. Adding it to ignore breaks the cycle.

Modal / dialog

<script setup lang="ts">
const props = defineProps<{ modelValue: boolean }>();
const emit = defineEmits<{ "update:modelValue": [value: boolean] }>();
</script>

<template>
  <div v-if="modelValue" class="overlay">
    <div
      v-click-outside="() => emit('update:modelValue', false)"
      class="dialog"
    >
      <slot />
    </div>
  </div>
</template>

Conditionally disable

<template>
  <div
    v-click-outside="{
      handler: onClickOutside,
      disabled: !isEditing,
    }"
  >
    ...
  </div>
</template>

How it works

  • Listens to pointerdown on document — fires before click and works for touch events
  • Uses event.composedPath() for correct Shadow DOM support, falls back to event.target
  • The listener is created once in mounted and removed in unmounted — no leaks
  • On updated, options are swapped in-place without recreating the listener
  • null / undefined entries in ignore are safely skipped

License

MIT