JavaScriptpreventDefault & stopPropagation

preventDefault and stopPropagation

Every event has built-in browser behaviour attached to it — links navigate, forms submit, right-clicks open menus, checkboxes toggle. Sometimes you want that behaviour, sometimes you want to replace it with your own. Two related-but-different methods control this: preventDefault() cancels the default action; stopPropagation() cancels further travel of the event. Mixing them up is a classic source of "why doesn't my link work?" bugs.

preventDefault — cancel the browser's reaction

Many events have a default action baked into the browser. When the event reaches the target, the browser asks: "did anybody call preventDefault?" If not, it carries out that default. Calling event.preventDefault() flips the flag.

  • Click on <a href> → navigate. preventDefault stops the navigation.

  • submit on <form> → POST and reload. preventDefault lets you handle the data in JavaScript instead.

  • contextmenu (right-click) → show the OS menu. preventDefault suppresses it.

  • keydown → type the character / scroll the page. preventDefault blocks that key.

  • dragstart, drop → default file handling. preventDefault lets you customise it.

Cancelling a link click

HTML
<a id="docs" href="/docs">Open docs</a>

JS
document.getElementById("docs").addEventListener("click", (e) => {
  e.preventDefault();             // do not navigate
  openInModal("/docs");           // do this instead
});
Submitting a form with fetch

HTML
<form id="signup">
  <input name="email" type="email" required />
  <button>Sign up</button>
</form>

JS
document.getElementById("signup").addEventListener("submit", async (e) => {
  e.preventDefault();                    // skip the page reload
  const data = new FormData(e.currentTarget);
  await fetch("/api/signup", { method: "POST", body: data });
});

Without that preventDefault the browser would do a classic full-page submit. With it, you keep control and stay in your single-page app.

Suppressing the right-click menu

JS
canvas.addEventListener("contextmenu", (e) => e.preventDefault());
Use sparingly
Suppressing the context menu also blocks "save image", "copy link", and assistive shortcuts. Only do it where the default menu actively gets in the way (a drawing canvas, a 3D viewer).
stopPropagation — do not let it travel further

stopPropagation() is unrelated to defaults — it tells the event "stop walking the DOM after this listener". Listeners further up (or further down in capture phase) will not see it.

JS
document.addEventListener("click", () => closeMenu());

menu.addEventListener("click", (e) => {
  e.stopPropagation();           // click inside the menu does not bubble to document
});

This is how the classic "click outside to close" pattern works without using a flag.

stopImmediatePropagation — stop sibling listeners too

stopImmediatePropagation() is the strictest of the three. It stops other listeners on the same element from running as well as preventing the event from propagating up or down.

JS
btn.addEventListener("click", (e) => {
  e.stopImmediatePropagation();
  console.log("only this one runs");
});
btn.addEventListener("click", () => console.log("never runs"));
preventDefault vs stopPropagation — they are independent

Returning to the link example: calling stopPropagation will not stop the navigation. The navigation is the default action, which lives in the browser's normal handling of the click — it is unrelated to whether other listeners see the event. To stop a link, you need preventDefault.

bug: navigates anyway

JS
link.addEventListener("click", (e) => {
  e.stopPropagation();          // does not cancel navigation
  doSomething();
});

fix: cancel the default

JS
link.addEventListener("click", (e) => {
  e.preventDefault();
  doSomething();
});
Passive listeners cannot preventDefault
passive: true blocks preventDefault
If a listener is registered with `{ passive: true }`, calling `preventDefault` inside it is silently ignored (and produces a console warning). The browser made performance guarantees based on that flag. Either drop the `passive` option or move the cancellation to a different event.
Which one do you actually need?
  • Stop a link from navigating, a form from submitting, a key from typing → preventDefault.

  • Stop a parent listener from also handling the same click → stopPropagation.

  • Stop other handlers on the same element from running → stopImmediatePropagation.

  • Need both? Call both. They are independent.

Mental model
`preventDefault` talks to the **browser**. `stopPropagation` talks to the **DOM**. `stopImmediatePropagation` talks to the **element itself**. Three different audiences, three different verbs.