JavaScriptBeginner

JavaScript preventDefault(): Stop Links and Forms Default Actions

Learn what default browser actions are and how event.preventDefault() lets you control link clicks, form submits, keys and right-clicks, with real examples.

All JavaScript lessons

What you will learn

Browsers are helpful. Click a link and they open a new page. Submit a form and they send the data and reload. Right-click and they show a menu. Press the space bar and the page scrolls down.

These built-in behaviours are called default actions. Most of the time they are exactly what you want. But sometimes you want to take control: check a form before it is sent, load a page’s content without reloading, or build your own right-click menu. For that, you use event.preventDefault().

In this lesson you will learn what default actions are, how to cancel them, and how to use preventDefault for forms, links, keyboard input and more, plus how it is different from stopPropagation.

Note: The code editor’s preview is sandboxed, so real navigation and real form sending may be blocked there anyway. Watch the Console messages and the changes on the page to see that your code ran as expected.

What is a default action?

A default action is what the browser does on its own when an event happens, even if you wrote no JavaScript at all.

You do this The browser does this by default
Click a link <a href="..."> Go to that address
Click a submit button, or press Enter in a form Submit the form and load the result page
Right-click Show the context menu
Press the space bar Scroll the page down
Type a key in an input Put that character in the input
Click a checkbox Tick or untick it
Drag a file onto the page Open the file

event.preventDefault() tells the browser: “Thanks, but I will handle this myself. Do not do your usual thing.”

How preventDefault works

You call it on the event object inside a listener:

element.addEventListener("click", function (event) {
  event.preventDefault();
  // ...your own code instead
});

Two important points:

  1. It cancels only the browser’s default action. The event still travels (bubbles) up the tree, and other listeners still run.
  2. It only works if you call it before the listener finishes. And it only works on events that are cancelable.

The page has a normal link:

<a id="docs-link" href="https://example.com">Read the docs (example.com)</a>

Without JavaScript, clicking it leaves your page. With preventDefault, nothing happens by itself, and you decide what to do:

const link = document.getElementById("docs-link");

link.addEventListener("click", function (event) {
  event.preventDefault();
  console.log("Link click cancelled");
  console.log("Was it prevented?", event.defaultPrevented);   // true
});

event.defaultPrevented is true once somebody has called preventDefault() on the event.

Real use case: ask before leaving

Show a confirmation before sending the user to another website:

const link = document.getElementById("docs-link");

link.addEventListener("click", function (event) {
  const sure = confirm("You are leaving this site. Continue?");

  if (!sure) {
    event.preventDefault();   // user said no: stay on the page
  }
});

Here we cancel the navigation only in one case. If the user presses OK, we do nothing, and the link works normally. preventDefault does not have to be the first line. It can be conditional.

Example 2: forms (the most common use)

When a form is submitted, the browser collects the data, sends it to the server and reloads the page. For most modern web apps, you want to:

  1. Stop the reload.
  2. Check the data yourself.
  3. Show errors, or send the data with JavaScript (using fetch).

Here is a sign-up form:

<form id="signup" novalidate>
  <label>Email
    <input id="email" name="email" type="email" placeholder="you@example.com">
  </label>
  <label>Password
    <input id="password" name="password" type="password" placeholder="At least 6 characters">
  </label>
  <label><input id="terms" name="terms" type="checkbox"> I accept the terms</label>
  <p id="error" class="error"></p>
  <button type="submit">Sign up</button>
</form>
.error {
  color: #b91c1c;
  min-height: 1.2em;
}

(novalidate turns off the browser’s own pop-up messages, so we can show ours. In a real project you may keep both.)

const form = document.getElementById("signup");
const errorBox = document.getElementById("error");

form.addEventListener("submit", function (event) {
  event.preventDefault();   // stop the page reload

  const email = form.elements.email.value.trim();
  const password = form.elements.password.value;
  const accepted = form.elements.terms.checked;

  if (!email.includes("@")) {
    errorBox.textContent = "Please enter a valid email.";
  } else if (password.length < 6) {
    errorBox.textContent = "Password must be at least 6 characters.";
  } else if (!accepted) {
    errorBox.textContent = "You must accept the terms.";
  } else {
    errorBox.textContent = "";
    console.log("Form is valid, ready to send");
  }
});

Try it with an empty form, then a short password, then a valid one.

Listen to submit, not click

A very common mistake is adding the listener to the button’s click:

const button = document.querySelector("#signup button");

// Weak: misses the Enter key
button.addEventListener("click", function (event) {
  event.preventDefault();
  // ...
});

Users can submit a form by pressing Enter inside a field, and that does not click the button. The submit event on the form catches every way of submitting. Always use form.addEventListener("submit", ...).

Read all the values with FormData

FormData collects every named field of a form for you:

const form = document.getElementById("signup");

form.addEventListener("submit", function (event) {
  event.preventDefault();

  const data = new FormData(form);
  const values = Object.fromEntries(data);

  console.log(values);   // { email: "...", password: "...", terms: "on" }
});

Note that every field needs a name attribute to appear in FormData. Unticked checkboxes are left out, and ticked ones have the value "on" unless you set a value. From here you could send values to a server with fetch, which you will learn in the async lessons.

A search form that updates results on the same page instead of reloading:

const searchForm = document.createElement("form");
searchForm.innerHTML = '<input name="q" placeholder="Search..."> <button>Go</button>';
document.body.append(searchForm);

searchForm.addEventListener("submit", function (event) {
  event.preventDefault();

  const query = searchForm.elements.q.value.trim();

  if (query === "") {
    console.log("Please type something");
    return;
  }

  console.log("Searching for:", query);
});

Example 3: control what a user can type

The keydown event happens before the character appears in the input. If you cancel it, the character is never typed.

<label>Phone (digits only)
  <input id="phone" type="text" placeholder="9876543210">
</label>
const phone = document.getElementById("phone");

phone.addEventListener("keydown", function (event) {
  const isCharacter = event.key.length === 1;           // "a", "5", "$" (not "Enter")
  const isShortcut = event.ctrlKey || event.metaKey;    // Ctrl+C, Ctrl+V, Cmd+A ...

  if (isCharacter && !isShortcut && !/\d/.test(event.key)) {
    event.preventDefault();   // block anything that is not a digit
  }
});

Keys like Enter, Backspace and ArrowLeft have names longer than one character, so they are left alone. /\d/ is a regular expression that matches a digit. See Regular Expressions for more.

Careful: This only blocks typing. A user can still paste letters or drop text into the box. Never rely on it for safety. Always validate the final value too, and on the server as well.

Example 4: your own right-click menu

Apps like Google Docs and Figma replace the browser’s context menu. The event is called contextmenu:

<div id="card">Right-click me</div>
const card = document.getElementById("card");

card.addEventListener("contextmenu", function (event) {
  event.preventDefault();   // hide the browser's menu

  console.log("Show my own menu at", event.clientX, event.clientY);
});

Then you would position your own <div> at clientX and clientY. Use this only where it truly helps. Blocking the normal menu on a whole website, for example to stop people copying text, is annoying and easy to bypass.

Example 5: pages without reload (single-page style)

Frameworks like React Router use this idea. A link normally loads a new page. We cancel that, and instead show a different section:

<nav id="nav">
  <a href="#home" data-page="home">Home</a>
  <a href="#about" data-page="about">About</a>
  <a href="#contact" data-page="contact">Contact</a>
</nav>

<div class="page" id="home">Welcome home.</div>
<div class="page" id="about" hidden>About us.</div>
<div class="page" id="contact" hidden>Contact us.</div>
const nav = document.getElementById("nav");

nav.addEventListener("click", function (event) {
  const link = event.target.closest("a[data-page]");
  if (!link) return;

  event.preventDefault();   // do not jump or reload

  document.querySelectorAll(".page").forEach((page) => {
    page.hidden = true;
  });
  document.getElementById(link.dataset.page).hidden = false;
});

This also uses event delegation from the last lesson: one listener on <nav> handles every link, and preventDefault is only called for links we care about. Because the links still have a real href, they still work for search engines and for users who have JavaScript turned off. That is called progressive enhancement, and it is a good habit.

Example 6: other places you will see it

// Stop dragging an image (so the user cannot drag it out of the page)
const img = document.createElement("img");
document.body.append(img);
img.addEventListener("dragstart", (event) => event.preventDefault());

// Stop the space bar from scrolling the page when a game is running
document.addEventListener("keydown", function (event) {
  if (event.code === "Space") {
    event.preventDefault();
    console.log("Jump!");
  }
});

// Stop the browser from opening a file dropped on the page,
// so you can read it yourself in a drag-and-drop uploader
document.addEventListener("dragover", (event) => event.preventDefault());
document.addEventListener("drop", (event) => event.preventDefault());

For a drop zone, you must cancel dragover, otherwise the browser will not allow dropping and the drop event never fires.

preventDefault vs stopPropagation

These two are often mixed up. They solve different problems.

preventDefault() stopPropagation()
Stops The browser’s built-in action The event from travelling to other elements
Does the event still bubble? Yes No
Do other listeners still run? Yes Only those on the same element
Example Link does not open Parent listener does not hear the click

You can use both together, but think first about whether you really need the second one:

const link = document.getElementById("docs-link");

link.addEventListener("click", function (event) {
  event.preventDefault();    // do not open the page
  event.stopPropagation();   // and do not let parent listeners know
});

return false is an old trick that you may see in code like <a onclick="return false">. Inside addEventListener functions, return false does nothing. Always use event.preventDefault().

Not every event can be cancelled

Some events are not cancelable, because there is nothing for the browser to cancel. You can check with event.cancelable.

document.addEventListener("click", (e) => console.log("click cancelable:", e.cancelable));    // true
document.addEventListener("scroll", (e) => console.log("scroll cancelable:", e.cancelable));  // false

Typical cancelable events: click, submit, keydown, keypress, contextmenu, dragover, drop, touchstart.

Typical non-cancelable events: scroll, load, focus, blur, input, mouseenter.

For scroll, the scrolling has already happened by the time the event fires. To stop scrolling you must cancel wheel or touchmove instead.

Passive listeners cannot cancel

In the events lesson you met { passive: true }. A passive listener promises not to call preventDefault(). If it does, the browser ignores it and prints a warning in the console:

window.addEventListener(
  "wheel",
  function (event) {
    event.preventDefault();   // ignored: the listener is passive
  },
  { passive: true }
);

If you really need to cancel scrolling, use { passive: false }. Do it rarely, because it can make scrolling feel slow.

Common mistakes

  • Calling preventDefault() too late. It must be called while your listener is running. After an await, a setTimeout or a fetch, it is too late, the browser has already acted. Call it first, then do the async work.
  • Listening to the button click instead of the form submit. Pressing Enter will still submit the form. Use form.addEventListener("submit", ...).
  • Forgetting event in the function. Writing function () { event.preventDefault(); } fails or uses an old global. Write function (event) { ... }.
  • Using return false. It does not cancel anything in addEventListener handlers.
  • Calling it on the wrong event. To stop letters appearing in an input, cancel keydown (or beforeinput), not keyup. By the time keyup fires, the letter is already there.
  • Breaking keyboard accessibility. Cancelling Tab, Enter, Space or Arrow keys can trap users. Only cancel the keys you truly handle.
  • Forgetting the name attribute on inputs, so FormData is empty.
  • Cancelling every link on the page. Check event.target.closest("a[data-page]") first, so normal links still work.
  • Relying on blocked typing for safety. Users can paste. Always validate on submit, and again on the server.
  • Adding preventDefault to a passive listener (wheel, touchstart on some browsers) and wondering why nothing happens.
  • Mixing it up with stopPropagation. If a link still opens, you need preventDefault. If a parent still reacts, you need to rethink your design, and maybe use stopPropagation.

Practice

  1. Click Practice in Editor. Click the docs link and confirm the Console prints Link click cancelled.
  2. Change the link code so that it asks confirm("Leave this page?") and only cancels when the user presses Cancel.
  3. Submit the sign-up form empty. Which message appears? Fill the fields one by one and watch the messages change.
  4. Add one more rule: the email must end with .com. Show a message if it does not.
  5. Print Object.fromEntries(new FormData(form)) for a valid submission. What does the terms checkbox value look like?
  6. Press Enter inside the email field. Does submit fire? Now try listening on the button’s click instead. What changes?
  7. In the phone box, try typing letters, then digits, then press Backspace and Ctrl+V. Which ones work, and why?
  8. Limit the phone box to 10 digits by also cancelling keydown when the value already has 10 characters (but still allow Backspace and arrows).
  9. Right-click the dashed card and check the Console. Then show the clicked position inside the card instead (use card.textContent).
  10. Add a fourth page link, Help, to the navigation. How much JavaScript did you need to change?
  11. Challenge: write a listener for keydown on document that reacts to Ctrl+S by calling preventDefault() and printing "Saved!" instead of opening the browser’s save dialog.
  12. Challenge: make the sign-up form disable its button for 2 seconds after a valid submit, to stop double submissions. Hint: setTimeout.

Recap

  • A default action is what the browser does by itself: follow a link, submit a form, show a context menu, scroll, type a character.
  • event.preventDefault() cancels that default action. The event still bubbles and other listeners still run.
  • Forms: listen to the form’s submit event, call preventDefault(), then validate and read values (for example with FormData).
  • Links: cancel the click to run your own code, ask for confirmation, or switch content without reloading.
  • Keyboard: cancel keydown to block a character or take over a shortcut. It does not stop pasting.
  • contextmenu, dragover, drop and dragstart are common events to cancel.
  • event.defaultPrevented tells you whether it was cancelled. event.cancelable tells you whether it can be.
  • Call it early and synchronously. Passive listeners ignore it. return false does not work in addEventListener.
  • preventDefault stops the browser’s action. stopPropagation stops the event from travelling. They are different tools.
  • You now have the full events toolkit: listeners, propagation, delegation and preventDefault().