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.
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:
- It cancels only the browser’s default action. The event still travels (bubbles) up the tree, and other listeners still run.
- It only works if you call it before the listener finishes. And it only works on events that are cancelable.
Example 1: a link that does not navigate
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:
- Stop the reload.
- Check the data yourself.
- 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.
Real use case: a search box
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 anawait, asetTimeoutor afetch, it is too late, the browser has already acted. Call it first, then do the async work. - Listening to the button
clickinstead of the formsubmit. Pressing Enter will still submit the form. Useform.addEventListener("submit", ...). - Forgetting
eventin the function. Writingfunction () { event.preventDefault(); }fails or uses an old global. Writefunction (event) { ... }. - Using
return false. It does not cancel anything inaddEventListenerhandlers. - Calling it on the wrong event. To stop letters appearing in an input, cancel
keydown(orbeforeinput), notkeyup. By the timekeyupfires, 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
nameattribute on inputs, soFormDatais 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
preventDefaultto a passive listener (wheel,touchstarton some browsers) and wondering why nothing happens. - Mixing it up with
stopPropagation. If a link still opens, you needpreventDefault. If a parent still reacts, you need to rethink your design, and maybe usestopPropagation.
Practice
- Click Practice in Editor. Click the docs link and confirm the Console prints
Link click cancelled. - Change the link code so that it asks
confirm("Leave this page?")and only cancels when the user presses Cancel. - Submit the sign-up form empty. Which message appears? Fill the fields one by one and watch the messages change.
- Add one more rule: the email must end with
.com. Show a message if it does not. - Print
Object.fromEntries(new FormData(form))for a valid submission. What does thetermscheckbox value look like? - Press Enter inside the email field. Does
submitfire? Now try listening on the button’sclickinstead. What changes? - In the phone box, try typing letters, then digits, then press Backspace and Ctrl+V. Which ones work, and why?
- Limit the phone box to 10 digits by also cancelling
keydownwhen the value already has 10 characters (but still allow Backspace and arrows). - Right-click the dashed card and check the Console. Then show the clicked position inside the card instead (use
card.textContent). - Add a fourth page link,
Help, to the navigation. How much JavaScript did you need to change? - Challenge: write a listener for
keydownondocumentthat reacts toCtrl+Sby callingpreventDefault()and printing"Saved!"instead of opening the browser’s save dialog. - 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
submitevent, callpreventDefault(), then validate and read values (for example withFormData). - Links: cancel the click to run your own code, ask for confirmation, or switch content without reloading.
- Keyboard: cancel
keydownto block a character or take over a shortcut. It does not stop pasting. contextmenu,dragover,dropanddragstartare common events to cancel.event.defaultPreventedtells you whether it was cancelled.event.cancelabletells you whether it can be.- Call it early and synchronously. Passive listeners ignore it.
return falsedoes not work inaddEventListener. preventDefaultstops the browser’s action.stopPropagationstops the event from travelling. They are different tools.- You now have the full events toolkit: listeners, propagation, delegation and
preventDefault().