useClickOutside
Run a handler whenever a pointerdown happens outside one or more elements. Useful for closing dropdowns, popovers and modal-like UI without binding click listeners on every other element on the page.
Demo
<script setup lang="ts">
import { ref, useTemplateRef } from 'vue';
import { useClickOutside } from '@basmilius/common';
const trigger = useTemplateRef<HTMLButtonElement>('trigger');
const panel = useTemplateRef<HTMLDivElement>('panel');
const open = ref(false);
useClickOutside([trigger, panel], open, () => {
open.value = false;
});
</script>
<template>
<div class="demo">
<button ref="trigger" class="trigger" @click="open = !open">
{{ open ? 'Close' : 'Open' }} menu
</button>
<div v-if="open" ref="panel" class="panel">
Click anywhere outside this panel to close it.
</div>
</div>
</template>
<style scoped>
.demo {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 12px;
padding: 24px;
border: 1px solid var(--vp-c-border);
border-radius: 12px;
background: var(--vp-c-bg-soft);
}
.trigger {
padding: 8px 16px;
border-radius: 8px;
background: var(--vp-c-brand-1);
font-weight: 600;
color: var(--vp-c-white);
}
.panel {
padding: 16px;
border: 1px solid var(--vp-c-border);
border-radius: 8px;
background: var(--vp-c-bg);
color: var(--vp-c-text-2);
font-size: 14px;
}
</style>Importing
ts
import { useClickOutside } from '@basmilius/common';Usage
vue
<script setup lang="ts">
import { ref, useTemplateRef } from 'vue';
import { useClickOutside } from '@basmilius/common';
const trigger = useTemplateRef<HTMLButtonElement>('trigger');
const popover = useTemplateRef<HTMLDivElement>('popover');
const open = ref(false);
useClickOutside([trigger, popover], open, () => {
open.value = false;
});
</script>
<template>
<button ref="trigger" @click="open = !open">Toggle</button>
<div v-if="open" ref="popover">Popover content</div>
</template>useTemplateRef('name') returns a ShallowRef whose .value is the element matching ref="name" once the component mounts. It is the recommended way to wire template refs since Vue 3.5.
The handler only runs while enabled is truthy. Pass a Ref<boolean> to react to changes (for example a "popover is open" flag) or true for an always-on listener.
A single ref or an array of refs is accepted. Component refs are unwrapped with unwrapElement, so passing component instances works out of the box.
Type signature
ts
type EligibleElement = HTMLElement | ComponentPublicInstance;
type Handler = ((evt: PointerEvent) => void) | ((evt: PointerEvent) => Promise<void>);
declare function useClickOutside<TElement extends EligibleElement>(
elementRefs: Ref<TElement | null> | Ref<TElement | null>[],
enabled: boolean | Ref<boolean>,
onOutsideClick: Handler
): void;