Handle browser events, event delegation, bubbling, capturing, and preventDefault with vanilla JavaScript.
addEventListener()Attach event handlers for flexible, composable event handling.
const button = document.querySelector("button");
button.addEventListener("click", (event) => {
console.log("Button clicked");
});The event objectContains details about what happened, like type, target, and position.
button.addEventListener("click", (e) => {
console.log(e.type); // "click"
console.log(e.target); // the button element
console.log(e.clientX); // mouse position
});removeEventListener()Removes a listener when it's no longer needed.
const handler = (e) => console.log("clicked");
button.addEventListener("click", handler);
button.removeEventListener("click", handler);Avoid inline handlersInline event handlers like onclick are outdated compared to addEventListener.
// Avoid: <button onclick="handleClick()">Click</button>
// Use addEventListener instead{ once: true }Fires a listener only one time, then removes it automatically.
button.addEventListener("click", handler, { once: true });
// Handler is automatically removed after first clickBubbling by defaultEvents bubble up from child to parent by default.
div.addEventListener("click", () => console.log("div clicked"));
button.addEventListener("click", () => console.log("button clicked"));
// Clicking button logs both "button clicked" and "div clicked"stopPropagation()Prevents an event from bubbling up to parent listeners.
button.addEventListener("click", (e) => {
e.stopPropagation();
console.log("button only");
});Capture phasePass true as the third argument to listen during the capture phase.
div.addEventListener("click", handler, true);
// Capture phase runs before bubble phaseMost events bubbleMost events bubble, but check the MDN docs for specific events that don't.
Blocking parent handlersUse stopPropagation to prevent parent handlers from running at all.
preventDefault()Stops the browser's default action for an event.
form.addEventListener("submit", (e) => {
e.preventDefault();
// Form does not submit to server
console.log("Form submission intercepted");
});Common use casesWorks on clickable links, form submissions, and other cancelable events.
link.addEventListener("click", (e) => {
e.preventDefault();
// Link does not navigate to href
});event.defaultPreventedCheck whether preventDefault was already called on the event.
if (!event.defaultPrevented) {
// Default action will occur
}Not all events cancelableNot all events can be prevented — check if the event is cancelable first.
event.cancelableVerify the event supports preventDefault before calling it.
link.addEventListener("click", (e) => {
if (e.cancelable) {
e.preventDefault();
}
});One listener, many childrenAttach a single listener to a parent to handle children efficiently.
const list = document.querySelector("ul");
list.addEventListener("click", (e) => {
if (e.target.tagName === "LI") {
console.log("Clicked item:", e.target.textContent);
}
});Fewer listenersThis pattern is efficient when there are many similar elements.
// Instead of attaching listeners to each item:
items.forEach(item => item.addEventListener("click", handler));
// Attach one listener to the container:
container.addEventListener("click", handler);e.target.closest()Finds the closest matching ancestor from the actual clicked element.
document.addEventListener("click", (e) => {
const button = e.target.closest("button");
if (button) console.log("Button clicked");
});Works with dynamic elementsDelegation works with elements added to the DOM after the listener was attached.
Mouse eventsclick, dblclick, mousedown, mouseup, and mousemove track pointer activity.
element.addEventListener("mousemove", (e) => {
console.log(`Mouse at ${e.clientX}, ${e.clientY}`);
});Keyboard eventskeydown and keyup track key presses; keypress is deprecated.
document.addEventListener("keydown", (e) => {
console.log(`Key pressed: ${e.key}`);
});Form eventschange, input, submit, reset, focus, and blur track form interaction.
input.addEventListener("input", (e) => {
console.log(`Current value: ${e.target.value}`);
});Window eventsload, unload, scroll, and resize track the page and viewport.
window.addEventListener("scroll", () => {
console.log("Page scrolled");
});Bubbling propagates events from the target element up to the root; capturing goes from root down to the target. Pass true as the third argument to addEventListener to use capturing phase instead of the default bubbling.
Call event.stopPropagation() inside your handler to prevent the event from bubbling up. Use event.stopImmediatePropagation() if you also want to block other listeners on the same element.
Attach one listener to a parent element and use event.target to identify which child triggered the event. This is especially useful for dynamically added elements or large lists. Listeners on the parent automatically cover new children.
Not all events are cancelable — for example, scroll events cannot be prevented after they fire. Check event.cancelable before calling preventDefault(), and for scroll/touch performance consider using a passive event listener ({passive: true}).
onclick can only hold one handler at a time and overwrites any previously assigned function. addEventListener supports multiple handlers on the same element. It also gives you control over phase (capture vs bubble) and one-time execution via {once: true}.
Validating a Form with Delegated Listeners
One delegated submit listener validates required fields and prevents submission until every field passes.
const form = document.querySelector('#signup-form');
form.addEventListener('submit', (e) => {
const invalid = [...form.querySelectorAll('[required]')].filter(field => !field.value.trim());
if (invalid.length > 0) {
e.preventDefault();
invalid.forEach(field => field.classList.add('error'));
console.log(`${invalid.length} field(s) missing`);
}
});
form.addEventListener('input', (e) => {
const field = e.target.closest('[required]');
if (field) field.classList.remove('error');
});Two delegated listeners — submit and input — validate an entire form without attaching a listener to each field.