Default actions in JavaScript events
Learn when the browser performs built-in event behavior, when preventDefault can cancel it, how passive listeners affect scrolling, and how defaultPrevented coordinates handlers.
- 01Cancel only when it is safeUse preventDefault on cancelable events without confusing it with propagation.
- 02Respect fast scrollingExplain passive listeners, wheel and touch events, and better CSS alternatives.
- 03Coordinate listenersRead defaultPrevented in delegated handlers so one listener can say, 'already handled.'
The browser already has plans
When a user clicks, types, scrolls, submits, or right-clicks, your JavaScript listener is not the only thing in the room. The browser often has a default action: built-in behavior it will perform if nothing cancels it.
A link’s default action is navigation. A form’s default action is submission. A checkbox click toggles its checked state. A printable key in an input inserts a character. Space can scroll the page. These defaults are why plain HTML pages work before you write one line of JavaScript.
Default actions usually run after event dispatch finishes, unless a listener successfully calls event.preventDefault() on a cancelable event.
Imagine boarding a plane that already knows the route. Most trips should use autopilot because it is reliable and fast. Sometimes the pilot must take control for a storm. That is preventDefault(): not turning autopilot off forever, just overriding one trip.
- In real life: Autopilot follows the usual route
- In JavaScript: The browser navigates, submits, toggles, types, or scrolls
- In real life: A pilot takes the wheel for this trip
- In JavaScript: A listener calls
event.preventDefault()for this one event - In real life: The next flight still has autopilot
- In JavaScript: Future clicks still have their defaults unless canceled again
Where the analogy stops: Autopilot sounds optional, but many browser defaults are essential accessibility features. Do not take the wheel unless you provide a safe replacement.
This lesson fits after Introduction to events, Bubbling & capturing, and Event delegation. You will use those ideas here: events dispatch through listeners, and later listeners can inspect what earlier listeners did.
preventDefault(): take the wheel
STEP THROUGHevent.preventDefault() requests: “Do not perform the built-in behavior for this event.” The request only succeeds if the event is cancelable. You can check that with event.cancelable.
It does not stop propagation. Other listeners may still run unless you separately call stopPropagation(). It also does not undo behavior that already happened. Default cancellation is about the browser’s next built-in step.
Step through a click that reaches an inner link listener and then an outer delegated router. Switch line 11 to predict whether the router skips.
script
type: "click", cancelable: true, defaultPrevented: false, preventDefault() { if (this.cancelable) this.defaultPrevented = true; },}; function linkListener(event) { event.preventDefault(); console.log("link listener finished");} function routerListener(event) { if (event.defaultPrevented) { console.log("router skipped"); return; } console.log("router handles the click");} linkListener(event);routerListener(event);console.log(event.defaultPrevented);In the replay, the inner listener can cancel the link default. The outer listener still runs because propagation was not stopped. It simply sees defaultPrevented and decides not to do more.
Autopilot switchboard
INTERACTIVEThe easiest way to understand default actions is to try real ones. This playground attaches listeners with addEventListener inside an effect. The listeners touch only real DOM nodes through refs, so React can keep rendering the lesson safely.
link.addEventListener("click", maybePreventLink);form.addEventListener("submit", maybePreventForm);checkbox.addEventListener("click", maybePreventCheckbox);function maybePrevent(event, switchIsOn) { if (switchIsOn && event.cancelable) event.preventDefault(); log(event.type, event.defaultPrevented);}Try real defaults
Hash: not changed yet
- Ready. Toggle a switch, then try a browser default.
Each control is a real browser default. The log reports the event’s cancelable and defaultPrevented values after the listener runs.
Try these small experiments: allow the link and watch the hash change; prevent the checkbox and notice it refuses to toggle; prevent the input and typed characters do not appear; allow the context menu and the browser may show its real menu.
A canceled default should have a replacement. If you block a form submit, show validation or save the data. If you block a context menu, provide your own menu and keyboard access. If you block text selection, make sure copying is not an expected task.
Passive listeners & scrolling
INTERACTIVEScrolling is special because it must feel immediate. If every wheel or touchmove listener might cancel scrolling, the browser may have to wait for JavaScript before moving pixels. Passive listeners solve that problem.
A passenger may still talk, point, and react. They just promised not to interfere with driving. In JavaScript, a passive listener can log and update state, but it cannot cancel the scroll default.
- In real life: Passenger says, 'I will not touch the steering wheel'
- In JavaScript: Listener is registered with
{ passive: true } - In real life: Driver keeps moving without waiting
- In JavaScript: The browser can start scrolling immediately
- In real life: Passenger reaches anyway; driver ignores it
- In JavaScript: Calling
preventDefault()is ignored in a passive listener
Where the analogy stops: The browser is not literally driving. Passive is a promise about default cancellation, not about whether your listener can read the event or update UI.
box.addEventListener("wheel", (event) => { log("cancelable", event.cancelable); if (shouldBlock) { event.preventDefault(); } log("defaultPrevented", event.defaultPrevented);}, { passive });Scrollable test box
Scroll row 1: the browser wants this to move smoothly.
Scroll row 2: the browser wants this to move smoothly.
Scroll row 3: the browser wants this to move smoothly.
Scroll row 4: the browser wants this to move smoothly.
Scroll row 5: the browser wants this to move smoothly.
Scroll row 6: the browser wants this to move smoothly.
Scroll row 7: the browser wants this to move smoothly.
Scroll row 8: the browser wants this to move smoothly.
Scroll row 9: the browser wants this to move smoothly.
Scroll row 10: the browser wants this to move smoothly.
Scroll row 11: the browser wants this to move smoothly.
Scroll row 12: the browser wants this to move smoothly.
Scroll row 13: the browser wants this to move smoothly.
Scroll row 14: the browser wants this to move smoothly.
- Scroll inside the box with a trackpad or wheel.
Non-passive mode lets the listener cancel a cancelable wheel or touchmove event, so the box can stop scrolling.
touch-action and overscroll-behavior are often better because the browser knows your intent before the event fires.In modern browsers, a passive listener that calls preventDefault() does not set defaultPrevented. Some browsers also treat touchstart, touchmove, or wheel listeners on window, document, or body as passive by default. Use { passive: false } only when blocking scroll is truly the interaction. CSS touch-action and overscroll-behavior are often better.
defaultPrevented as a sticky note
INTERACTIVEWhen preventDefault() succeeds, the event object keeps a boolean sticky note: event.defaultPrevented === true. Later listeners can read it and politely skip their own behavior.
The event object is shared as it travels. A child widget can mark the event as handled, and a parent-level router can read that mark without knowing the child’s internal code.
- In real life: A teammate sticks 'handled' on a task card
- In JavaScript: An inner listener calls
preventDefault() - In real life: Another teammate reads the card and does not duplicate work
- In JavaScript: A delegated listener checks
event.defaultPrevented - In real life: The sticky note stays on that one card
- In JavaScript: The flag belongs to this event object only
Where the analogy stops: The sticky note does not say who handled the event or why. If later code needs that detail, pass it through your own state or custom data.
panel.addEventListener("click", (event) => { if (event.target.matches("a")) event.preventDefault();});panel.addEventListener("click", (event) => { if (event.defaultPrevented) return; runRouterBehavior();});const scroll = new Event("scroll", { cancelable: false });scroll.preventDefault();Shared event object
- Click the link-like button, then test the non-cancelable scroll event.
The outer router does not need to know which inner widget ran. It reads event.defaultPrevented on the shared event object.
cancelable: false keeps defaultPrevented false.The same idea is common in delegated code: one ancestor listener handles many children, but it backs off when a child has already claimed the default action.
Can this event be canceled?
SORT ITNot every event can cancel a default. A scroll event is the classic trap: it fires after scrolling happened, so it is not cancelable. If you need to prevent scrolling, cancel the earlier wheel, touchmove, or key event that causes the scroll.
- Link navigation from a
click - Form submit from a
submitevent - Checkbox toggling from a
click - Text selection beginning on
mousedown - The context menu from
contextmenu - The
scrollevent itself - Page unload after the page is closing
- A character inserted by
keydownin an input
Sort each behavior by whether this event is the right place to call preventDefault().
| Goal | Usually cancel this | Not this |
|---|---|---|
| Stop link navigation | The link's click | A later page-load event |
| Stop form navigation | The form's submit | A click on one submit button only |
| Stop text insertion | A printable keydown or before-input style event | Reading the input value afterward |
| Stop scrolling | The causing wheel, touchmove, or key event | The scroll event itself |
| Stop context menu | contextmenu in most browsers | A random click after the menu opens |
Where you will use this
Use default cancellation when you replace the browser’s built-in behavior with an intentional, accessible behavior of your own. Here are three patterns you will see constantly.
form.addEventListener("submit", (event) => { event.preventDefault(); saveWithJavaScript(new FormData(form));});This submit handler cancels the page navigation, then saves with JavaScript. The replacement behavior must be visible: show loading, success, and error messages.
input.addEventListener("keydown", (event) => { if (event.key === "x") { event.preventDefault(); }});Be careful with keyboard defaults. Users rely on arrows, Tab, Space, Enter, and browser shortcuts. Block only the exact key you replace, and provide another path.
- Single-page routing
Cancel a same-origin link click, update history, then render the next view without a full document load.
- Drawing and maps
For a canvas or map, carefully cancel gestures the app owns. Use CSS gesture controls when possible.
- Delegated widgets
Child widgets can call
preventDefault(); parent listeners checkdefaultPreventedbefore acting.
Misconceptions and sharp edges
READ BEFORE SHIPPING- “preventDefault stops bubbling.” No. It cancels an eligible default action. Propagation is controlled separately.
- “stopPropagation stops the default.” No. A link can still navigate even if it never reaches an ancestor listener.
- “return false always works.” Not in
addEventListener. Prefer explicitpreventDefault(). - “Passive is just faster syntax.” No. Passive means the listener cannot cancel the default.
- “Every event has a preventable default.” No. Check
event.cancelableand choose the right earlier event. - “Blocking defaults is harmless.” Defaults are often accessibility features. Replace them thoughtfully.
button.addEventListener("click", (event) => { return false; // does not cancel an addEventListener default}); button.onclick = function (event) { return false; // old property handlers cancel the default};| Tool | What it changes | What it does not change |
|---|---|---|
preventDefault() | Eligible browser default behavior | Propagation by itself |
stopPropagation() | Whether ancestors receive the event | The default action by itself |
{ passive: true } | Whether the listener may cancel defaults | Whether the listener may read the event |
defaultPrevented | Reports successful cancellation | Makes an event cancelable |
Practice
5 EXERCISESWrite the three console lines in order. Separate them with commas.
const event = { defaultPrevented: false, cancelable: true };
event.preventDefault = function () {
if (this.cancelable) this.defaultPrevented = true;
};
function linkListener(event) {
event.preventDefault();
console.log("link listener finished");
}
function routerListener(event) {
if (event.defaultPrevented) {
console.log("router skipped");
return;
}
console.log("router handles the click");
}
linkListener(event);
routerListener(event);
console.log(event.defaultPrevented);The inner listener calls preventDefault(), logs its line, and leaves defaultPrevented true. The router sees the flag, logs router skipped, returns, and the final log prints true.
Predict the one console line.
const scroll = new Event("scroll", { cancelable: false });
scroll.preventDefault();
console.log(scroll.defaultPrevented);A scroll event created with cancelable: false stays unprevented. Calling preventDefault() does not throw; it just leaves defaultPrevented as false.
Which method belongs inside the if block to block the x key?
input.addEventListener("keydown", (event) => {
if (event.key === "x") {
event.preventDefault();
}
});input.addEventListener("keydown", (event) => {
if (event.key === "x") {
event.preventDefault();
}
});A printable keydown default inserts text into the input. Calling event.preventDefault() inside that event blocks the insertion for the chosen key.
The developer expected the link not to navigate. Explain the bug, then compare your fix with the solution.
link.addEventListener("click", (event) => {
saveDraft();
return false;
});link.addEventListener("click", (event) => {
event.preventDefault();
saveDraft();
});return false is ignored in an addEventListener callback. Call event.preventDefault() before replacing the link's navigation with saveDraft().
Sketch the event plan for a custom menu in a drawing app. Include the event you cancel and one accessible alternative.
menuArea.addEventListener("contextmenu", (event) => {
event.preventDefault();
openCustomMenu({ x: event.clientX, y: event.clientY });
});
menuButton.addEventListener("click", () => openCustomMenu());Cancel contextmenu only where the custom menu is truly available, and provide a normal button or keyboard path so users are not trapped without a menu.
Check your understanding
7 QUESTIONSQuestion 1 of 7When does the browser usually perform a default action for an event?
Choose an answer to see the explanation.
Question 2 of 7What does the non-cancelable scroll code print?
Read the code, then predictconst event = new Event("scroll", { cancelable: false }); event.preventDefault(); console.log(event.defaultPrevented);Choose an answer to see the explanation.
Question 3 of 7Which statement is true about
preventDefault()and propagation?Choose an answer to see the explanation.
Question 4 of 7What does the cancelable click code print?
Read the code, then predictconst event = new Event("click", { cancelable: true }); event.preventDefault(); console.log(event.defaultPrevented);Choose an answer to see the explanation.
Question 5 of 7A passive
wheellistener callsevent.preventDefault(). What should you expect?Choose an answer to see the explanation.
Question 6 of 7What does
return falsedo inside a listener added withaddEventListener?Choose an answer to see the explanation.
Question 7 of 7Why might a delegated router check
event.defaultPrevented?Choose an answer to see the explanation.
Key takeaways
- Default actions are the browser’s built-in behavior for events.
preventDefault()works only on cancelable events, and passive listeners cannot cancel.- Default actions happen after dispatch finishes, so later listeners can still run and inspect
defaultPrevented. - Canceling defaults and stopping propagation are different decisions.
- Respect accessibility: never remove keyboard, scrolling, selection, form, or menu behavior without a clear replacement.
A default action is the browser’s built-in response to an event, and preventDefault() cancels it only for a cancelable event in a listener allowed to cancel.
Up next: Custom events.