event-listener
Attach an event listener to a reactive target and clean it up with the current reactive scope.
| Package | @vizejs/composable/event-listener |
| Own the source | vize lib pull composable:event-listener |
| Runtime exports | useEventListener |
| Gzip budget | 2048 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 { useEventListener } from "@vizejs/composable/event-listener";
Try it
Observe input from a selected workshop draft
Follow native input events from one editable draft, retarget the listener, and pause or resume observation without locking either field.
What to observe: Edit First workshop draft to update the event count and last observed text. Editing the other field does not update the monitor. Choose Second workshop draft to move the listener. Pause, edit either draft, switch the observed draft, then Resume: only new input from the current target is observed.
useEventListener receives a computed HTMLInputElement target and a typed input handler. Retargeting removes the old listener. stop() removes the watcher and listener; start() restores observation of the current target, and repeated active starts are harmless. Each v-model remains independently writable. The monitor reads currentTarget.value from the real event and sends no request or save operation. Inside a component, attachment waits for mounted state, keeping isListening false during SSR and the initial hydration render. The owning scope removes the watcher and listener on disposal.
Compare the initial and interacted states


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 { computed, ref, useId, useTemplateRef } from "vue";
import { useEventListener } from "@vizejs/composable/event-listener";
const draftsId = useId();
const firstInput = useTemplateRef<HTMLInputElement>("firstInput");
const secondInput = useTemplateRef<HTMLInputElement>("secondInput");
const firstDraft = ref("Compiler workshop");
const secondDraft = ref("Editor workshop");
const selectedTarget = ref("first");
const target = computed(() =>
selectedTarget.value === "first" ? firstInput.value : secondInput.value,
);
const targetName = computed(() => (selectedTarget.value === "first" ? "first" : "second"));
const observedEvents = ref(0);
const observedText = ref("No input observed yet.");
const { isListening, start, stop } = useEventListener(target, "input", (event) => {
if (!(event.currentTarget instanceof HTMLInputElement)) return;
observedEvents.value += 1;
observedText.value = event.currentTarget.value;
});
</script>
<template>
<div class="composable-example">
<label :for="`${draftsId}-first`">First workshop draft</label>
<input :id="`${draftsId}-first`" ref="firstInput" v-model="firstDraft" />
<label :for="`${draftsId}-second`">Second workshop draft</label>
<input :id="`${draftsId}-second`" ref="secondInput" v-model="secondDraft" />
<label :for="`${draftsId}-target`">Observed draft</label>
<select :id="`${draftsId}-target`" v-model="selectedTarget">
<option value="first">First workshop draft</option>
<option value="second">Second workshop draft</option>
</select>
<output aria-live="polite"
>Listening to {{ targetName }} draft: {{ isListening ? "yes" : "no" }} · Observed input
events: {{ observedEvents }}</output
>
<p>Last observed text: {{ observedText }}</p>
<div class="example-actions">
<button type="button" :disabled="!isListening" @click="() => stop()">
Pause input observation
</button>
<button type="button" :disabled="isListening" @click="() => start()">
Resume input observation
</button>
</div>
<p>
Both drafts remain editable. Only input events from the selected draft update the observation;
pausing removes that listener and resuming attaches it to the current draft. This local
monitor does not save or send draft text.
</p>
</div>
</template>
Runtime contract
| Utility | Category | Stability | SSR | Hydration | Cleanup | Targets | Host globals | Uses |
|---|---|---|---|---|---|---|---|---|
useEventListener |
events | experimental | safe | stable | caller, reactive-scope | web, server, worker, native, desktop, terminal | — | tryOnScopeDispose |
API
useEventListener
Attach an event listener to a reactive target and clean it up with the current reactive scope. Missing targets are valid during server rendering: a null/undefined target keeps the listener detached until a concrete target appears, so no browser globals are required. The listener is re-attached whenever the reactive target changes and is removed when the owning reactive scope stops, when EventListenerControls.stop is called, or when the abort signal fires. Outside an active scope, teardown ownership stays with the caller, who must call stop explicitly. Errors thrown by a custom target's add/remove methods propagate to the active watcher run.
function useEventListener<Key extends keyof WindowEventMap>( target: MaybeRefOrGetter<Window | null | undefined>, event: Key, listener: (event: WindowEventMap[Key]) => void, options?: UseEventListenerOptions, ): EventListenerControls
useEventListener
function useEventListener<Key extends keyof DocumentEventMap>( target: MaybeRefOrGetter<Document | null | undefined>, event: Key, listener: (event: DocumentEventMap[Key]) => void, options?: UseEventListenerOptions, ): EventListenerControls
useEventListener
function useEventListener<Key extends keyof HTMLElementEventMap>( target: MaybeRefOrGetter<HTMLElement | null | undefined>, event: Key, listener: (event: HTMLElementEventMap[Key]) => void, options?: UseEventListenerOptions, ): EventListenerControls
useEventListener
function useEventListener( target: MaybeRefOrGetter<EventTarget | null | undefined>, event: string, listener: EventListener, options?: UseEventListenerOptions, ): EventListenerControls
useEventListener
function useEventListener( target: MaybeRefOrGetter<EventTarget | null | undefined>, event: string, listener: EventListener, options: UseEventListenerOptions = {}, ): EventListenerControls
Types
UseEventListenerOptions
Options for useEventListener.
| Member | Type | Description |
|---|---|---|
capture? |
boolean |
Invoke the listener during the capture phase. |
once? |
boolean |
Stop listening after the first event. |
passive? |
boolean |
Declare that the listener does not cancel the event's default action. |
signal? |
AbortSignal |
Stop listening when this signal is aborted. An already-aborted signal prevents listening from ever starting. |
immediate? |
boolean |
Start listening during composable creation. |
flush? |
"pre" | "post" | "sync" |
Reactive target update timing. |
EventListenerControls
Reactive controls returned by useEventListener.
| Member | Type | Description |
|---|---|---|
isListening |
Readonly<Ref<boolean>> |
Whether a concrete target currently owns the listener. |
start |
() => boolean |
Begin listening. |
stop |
() => void |
Stop listening. Repeated calls are safe. |