Topic 9.4
Default Actions
In one line
Many events come with a built-in browser behaviour, called the default action: a link navigates, a form sends and reloads the page, a checkbox toggles. event.preventDefault() cancels it so your JavaScript can do something else, and passive listeners promise never to cancel, which keeps scrolling smooth.
Think of it like this
A vending machine with a "cancel" button. When you press a snack's number, the machine's normal job is to drop that snack. That's the default action. If you press cancel in time, the machine doesn't drop it, but it still knows you pressed the number. preventDefault() is the cancel button: the event still happens and your listeners still hear it, but the browser skips its normal job.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Default action
- What the browser does by itself after an event, like following a link or sending a form.
- preventDefault()
- The method that cancels the default action of an event.
- cancelable
- A property that says whether
preventDefault()can cancel this event. - defaultPrevented
trueonce some listener has calledpreventDefault()on the event.- Submit
- Sending a form's data, by default by loading a new page with it.
- Passive listener
- A listener added with
{ passive: true }, promising it will never callpreventDefault(). - Compositor thread
- A separate part of the browser that can scroll and animate the page without waiting for JavaScript.
Step by step
01See the default action
A plain form with a submit button. Press Enter or click Send and the browser sends ?email=... to the action URL and loads a new page. Any JavaScript state on the old page is gone.
<form id="signup" action="/subscribe">
<input name="email" type="email" required>
<button>Send</button>
</form>02Listen for submit on the form
The listener goes on the form, for the submit event. It runs after the built-in checks (required, type="email") have passed, and before the browser sends anything.
const form = document.getElementById("signup");
form.addEventListener("submit", (event) => {
// runs before the page would reload
});03Cancel it
Call event.preventDefault() first, so even if later code throws, the page doesn't reload and hide the error.
form.addEventListener("submit", (event) => {
event.preventDefault();
console.log("Sending", form.elements.email.value, "with fetch instead");
});04Where it fits in the event's life
The browser runs every listener in the path first. Only when dispatch is completely finished does it look at defaultPrevented and decide whether to act.
05Mark scroll listeners passive
A scroll-related listener that only reads values (to show a progress bar, for example) should say so. The browser can then scroll smoothly without waiting for it.
window.addEventListener("wheel", onWheel, { passive: true });
document.addEventListener("touchmove", onTouchMove, { passive: true });Try it yourself
- 1
Let the checkbox toggle
In the first example, delete the checkbox's
preventDefault(). Predict both checkbox lines, then run. - 2
Submit without JavaScript's help
In the second example, comment out
event.preventDefault();. Predict what the preview shows. Then put it back and addif (!query) { status.textContent = "Type something first"; return; }before the search line, and clear the box before submitting. - 3
Check before acting
In the first example, add a second click listener on the link that logs
event.defaultPrevented. Add it before, then after, the first one. Predict both results.
Code & diagrams
A checkbox is special: the browser flips `checked` before your listener runs, then flips it back if you cancel. So inside the listener, `checked` already shows the new value.
Preview
Console
Nothing logged yet.Expected console output
link: navigation cancelled, defaultPrevented = true
checkbox during the listener: true
checkbox after the click: false`submitter` is the button that sent the form; it's `null` here because code called `requestSubmit()` without naming one. Click the Search button yourself and it shows `<button>`. Delete the `preventDefault()` line and the preview reports a submitted form instead.
Preview
Console
Nothing logged yet.Expected console output
submit handled in JS, query = debounce
submitter: null
still on the same pageIn the passive case Chrome also writes "Unable to preventDefault inside passive event listener invocation." to the browser's own DevTools console.
Preview
Console
Nothing logged yet.Expected console output
listener ran, defaultPrevented = true
dispatchEvent returned false (false means cancelled)
listener ran, defaultPrevented = false
dispatchEvent returned true
input cancelable? falseBreak it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Listen for click on the submit button
Replace the form's submit listener with button.addEventListener("click", (e) => { e.preventDefault(); send(); }).
Break #2
preventDefault in a passive listener
Add document.addEventListener("touchmove", (e) => e.preventDefault()); to stop the page scrolling while drawing on a canvas.
Myth vs fact
Myth
return false in a listener cancels the default action.
Fact
Only in on... handler properties and HTML attributes (onclick="return false"). In addEventListener listeners the return value is ignored. jQuery's handlers also treated return false as both preventDefault and stopPropagation, which is where the habit comes from.
Myth
preventDefault() stops other listeners from running.
Fact
It only cancels the browser's default action. The event keeps propagating and every listener still runs. Stopping listeners is stopPropagation()'s job.
Myth
Preventing default on keydown is a good way to stop paste or drop into a field.
Fact
Text can enter a field in many ways (paste, drag and drop, autofill, voice input). Use the beforeinput event, which covers all of them, or validate the value on input and on submit.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
For checkboxes and radio buttons, the HTML spec runs "legacy-pre-activation behaviour": the state flips before
clicklisteners run and is restored if the event is cancelled. Socheckedinside the click listener shows the new state. Listen tochangeinstead if you only care about the final state. - ▸
dispatchEvent()returnsfalseif the event was cancelable and some listener calledpreventDefault(). That's how custom components ask "may I?" before doing something (Topic 9.8). - ▸
Passive listeners matter for the INP metric and for smooth scrolling. Chrome's DevTools Performance panel flags non-passive touch and wheel listeners that delayed scrolling.
addEventListenerwith an options object is supported everywhere current; if you must support very old browsers, feature-detect passive support before passing{ passive: true }. - ▸
form.submit()(no "request") skips validation and does not fire thesubmitevent, so your listener never runs. Useform.requestSubmit(button?)to submit from code like a real button press.
Remember this
- 1
A default action is what the browser does by itself in response to an event. Clicking an
<a href>navigates to the link. Pressing a submit button submits the form: the browser sends the data and loads a new page. Clicking a checkbox toggles it. Pressing a key in a text box types a character. Right-clicking opens the context menu. Pressing Space on the page scrolls. Your listeners run first; the browser does the default action after dispatch finishes, unless someone cancelled it. - 2
event.preventDefault()cancels the default action. The event still propagates normally (bubbling is unaffected), other listeners still run, andevent.defaultPreventedbecomestrueso later listeners can check it. It only works ifevent.cancelableistrue. Some events can't be cancelled because the thing already happened:input(the text has already changed),scroll,focus,load. For those, callingpreventDefault()silently does nothing. - 3
The most common use is a form handled by JavaScript. Without
preventDefault(), submitting reloads the page and your JavaScript state is lost. With it, you read the fields yourself (Topic 9.6), validate, and send them withfetch(Topic 11.2). Always listen for the form's **submit** event, not the button'sclick:submitalso fires when the user presses Enter in a field, and only fires after the browser's built-in validation passes. - 4
Other uses: stop a link from navigating in a single-page app so a router can show the page instead (Topic 11.5); stop a checkbox from toggling until the server confirms; block a key such as letters in a number-only box (
keydown+preventDefault()); show your own menu oncontextmenu; or allow dropping files (dragovermust be cancelled beforedropfires). Each of these takes away something users expect, so replace the behaviour, don't just remove it. - 5
A passive listener is one added with
{ passive: true }. It promises the browser it will never callpreventDefault(). Why does that matter? Fortouchstart,touchmoveandwheel, the default action is scrolling. Normally the browser must wait for your listener to finish, in case it cancels the scroll, before it can move the page. With a passive listener, it scrolls straight away on its own compositor thread, while your code runs on the main thread. If a passive listener callspreventDefault()anyway, it's ignored and Chrome prints a console warning. - 6
Browsers already treat
touchstart,touchmove,wheelandmousewheellisteners added towindow,documentordocument.bodyas passive by default (an intervention from 2017 onwards). If you really need to block scrolling there (a custom drawing canvas, a map), add{ passive: false }explicitly, or better, use the CSS propertytouch-action: noneon the element so the browser knows without asking JavaScript.
Explain it without notes
What is a default action? Give three examples.
Why should you handle a form's submit event rather than the submit button's click?
What is a passive event listener and why do browsers want them?
What is the difference between preventDefault() and stopPropagation()?
Practice
Make a link <a href="/delete" id="remove">Delete</a> log Are you sure? instead of navigating. Click it from code.
Make a text input #digits that refuses any key that isn't a digit, Backspace, Tab or an arrow key. Test it by dispatching a keydown for a and for 5 and logging whether each was cancelled.
Trade-offs
- ↔
Handling a form in JavaScript allows inline errors and no page reload, but you must rebuild what the browser gave for free: loading states, error handling and a working page if JavaScript fails (progressive enhancement keeps a real
actionURL). - ↔
Cancelling defaults gives full control but removes behaviour users rely on (middle-click to open links in a new tab, right-click menus); only cancel when you replace the behaviour with something at least as good.
Done when you can
I can name common default actions and cancel them with
preventDefault().I handle forms with the
submitevent on the<form>, not the button's click.I can check
cancelableanddefaultPrevented.I can explain passive listeners and when to use
{ passive: true }.I know
preventDefaultandstopPropagationare independent.