onsubmit="return validate()" cancels the submit when the handler returns false. In addEventListener("submit", ...) the return value is ignored, so returning false does nothing and you must call event.preventDefault(). jQuery turns return false into preventDefault() plus stopPropagation(); React ignores the return value entirely.Point any HTML form at one endpoint. 500 free submissions — no card, no time limit — and unlimited forms.
Create free accountInline onsubmit: return false cancels the submit
An onsubmit attribute is not a function call that the browser watches. The attribute text becomes the body of a function, so the browser takes its return value and cancels the default action when that value is exactly false:
<form action="/contact" method="POST" onsubmit="return validate()">
<input name="email" type="email" required>
<button type="submit">Send</button>
</form>
<script>
function validate() {
const ok = document.querySelector('[name="email"]').value.includes("@");
if (!ok) alert("Enter a valid email");
return ok; // false cancels the submit, true lets it through
}
</script>That is the whole contract: false stops the navigation, anything else (including undefined) lets it continue. This is also why the pattern survives in old tutorials and in generated markup from form builders. For a full validation pass in plain JavaScript, see how to validate an HTML form with JavaScript.
One ordering detail catches people out. The submit event, and therefore onsubmit, fires after the browser has run constraint validation. A blank required field blocks the submission before your function is ever called, so onsubmit="return false" is not a way to bypass required fields. To skip native validation deliberately you need novalidate on the form or formnovalidate on the button, and then you own every check.
The most common bug: onsubmit="validate()" without return
Drop the return keyword and the attribute body evaluates to undefined, so the form submits even when validate() returns false:
<!-- Submits anyway: the attribute returns undefined -->
<form onsubmit="validate()">
<!-- Works: the attribute returns validate()'s value -->
<form onsubmit="return validate()">
<!-- Also works: cancel explicitly inside the attribute -->
<form onsubmit="return false">Two related traps live in the same place. A function that only shows an error and returns nothing is treated as true, so it never blocks. And if validate throws, the attribute body never reaches a return, the browser treats the result as not-false, and the submit goes ahead. Inline handlers also break under a Content-Security-Policy that forbids 'unsafe-inline', which is why the event listener version below is the safer default.
addEventListener: returning false does nothing
A listener registered with addEventListener is not an inline attribute. The browser calls it, ignores whatever it returns, and only looks at the event object. Returning false from it is dead code:
// Does NOT stop the form
form.addEventListener("submit", () => {
return false; // ignored
});
// Stops the form
form.addEventListener("submit", (event) => {
event.preventDefault();
});A useful pattern is to cancel only when your own rules fail. The submit event has already passed native validation at this point, so preventDefault() here is for custom checks, and leaving it out lets the browser POST normally:
form.addEventListener("submit", (event) => {
const error = checkCustomRules(form); // e.g. password confirmation
if (error) {
event.preventDefault(); // only when invalid
showError(error);
}
// valid: no preventDefault(), native POST continues
});Older code sometimes sets event.returnValue = false instead. It is a legacy alias for preventDefault() that browsers still support, but it is deprecated, reads like a return value, and confuses the next person who debugs the form. Use preventDefault().
If a submit still does nothing at all after this change, the bug is usually markup rather than JavaScript; work through why an HTML form is not submitting.
jQuery: return false means preventDefault + stopPropagation
jQuery gives return false a meaning of its own. In a jQuery event handler it calls event.preventDefault() and event.stopPropagation(), so the submit is cancelled and the event stops bubbling:
// Cancels the submit AND stops the event bubbling
$("#contact").on("submit", function () {
if (!$(this).valid()) return false;
return true;
});
// Cancels the submit, still lets parent handlers run
$("#contact").on("submit", function (event) {
if (!$(this).valid()) event.preventDefault();
});The difference matters when something else listens further up, such as an analytics wrapper on document that counts submissions. return false hides the event from it, and the count silently drops to zero. Prefer event.preventDefault() when you only want to stop the navigation. Note also that jQuery's return value is a jQuery convention, not a browser one; it works because jQuery reads it and calls the two methods for you.
The older $("form").submit(fn) shorthand behaves the same way, and so does a handler attached with .on("submit", fn): the return value is inspected, not passed to the browser. If a jQuery form submits twice, look for a handler bound again after an AJAX partial render; return false cannot save a duplicate binding, but event.stopImmediatePropagation() can hide the second one, which is usually the wrong fix.
React: onSubmit plus event.preventDefault()
React passes a synthetic event to onSubmit and discards the handler's return value, so return false is ignored here too:
function ContactForm() {
const [status, setStatus] = useState("");
async function handleSubmit(event) {
event.preventDefault(); // must be the first line
const data = new FormData(event.currentTarget);
setStatus("Sending...");
const res = await fetch("https://splitforms.com/api/submit", {
method: "POST",
headers: { Accept: "application/json" },
body: data,
});
setStatus(res.ok ? "Thanks!" : "Could not send.");
}
return (
<form onSubmit={handleSubmit}>
<input type="hidden" name="access_key" value="YOUR_ACCESS_KEY" />
<input name="email" type="email" required />
<button type="submit">Send</button>
<p role="status">{status}</p>
</form>
);
}Two React-specific notes. Read fields from event.currentTarget rather than event.target, because target can be a child element and the form reference is lost after an await. And do not reach for return false to stop a parent form from submitting: a nested form is invalid HTML, and React will warn about it. Split the markup into fieldsets instead.
If you use a form library such as React Hook Form, its handleSubmit already calls preventDefault() before it runs your callback. Adding return false on top changes nothing, and returning false from the callback does not stop the request either; the library only checks the validation result. Server Actions take a different path again: React handles the submit and the action runs on the server, so return false has no meaning there either.
If the handler throws before preventDefault(), the page reloads
This is the failure that looks like a random page refresh. The handler starts, validation or a fetch throws, the exception aborts the handler before preventDefault() runs, and the browser completes the native submission. The user sees the page reload and the typed data is gone:
// Fragile: an exception above preventDefault() submits the form
form.addEventListener("submit", async (event) => {
const ok = await checkServerSide(); // can reject
if (!ok) return; // returns undefined, not false
event.preventDefault();
send();
});
// Correct: cancel first, then do work that can fail
form.addEventListener("submit", async (event) => {
event.preventDefault(); // no reload, ever
const status = document.getElementById("status");
status.textContent = "Sending...";
try {
const res = await fetch("https://splitforms.com/api/submit", {
method: "POST",
headers: { Accept: "application/json" },
body: new FormData(form),
});
const result = await res.json();
status.textContent =
res.ok && result.success ? "Thanks! Your message was sent."
: result.message || "Could not send.";
if (res.ok && result.success) form.reset();
} catch {
status.textContent = "Network error. Please try again.";
}
});The form needs action="https://splitforms.com/api/submit", method="POST" and a hidden access_key field, so the same markup still works when JavaScript fails. Get a free access key; the payload rules are in the splitforms docs.
The same trap has an async form. An async function always returns a promise, and the browser does not wait for it, so async function validate() { return false; } still submits: the handler returns a promise object, which is not false. In an inline attribute that means an async validation can never cancel the submit, no matter what it returns. Keep preventDefault() in a synchronous listener, then await the check and call form.submit() only after it passes, or send the data with fetch as above.
form.submit() skips the submit event entirely
No event means no preventDefault() and no handler. form.submit() sends the form straight away, skipping validation and every listener you registered. Use form.requestSubmit() when you want the same path a click takes, including validation and your handler:
form.submit(); // no event, no validation, no handler
form.requestSubmit(); // fires submit; your preventDefault() runsThe full comparison, including which button values are lost, is in form.submit() vs requestSubmit(). If the problem is Enter instead of a click, see how to stop the Enter key submitting a form.
Comparison: what each approach actually cancels
| Approach | Stops the submit? | What else it does |
|---|---|---|
return false (inline onsubmit) | Yes | The attribute body's return value is checked by the browser. Must be exactly false. |
return false (addEventListener) | No | The return value is ignored. Use event.preventDefault() instead. |
event.preventDefault() | Yes | Cancels the navigation and the POST, leaves the event visible to other listeners. |
event.stopPropagation() | No | Only stops bubbling to parent listeners; the form still submits. |
return false (jQuery handler) | Yes | jQuery calls preventDefault() and stopPropagation() for you, which hides the event from parent handlers. |
For the HTML side of the same story, the form element reference covers action, method and the attributes that change how a submission starts.
FAQ
Why does my form still submit with return false?
Because the return value only means something in an inline onsubmit attribute. Inside addEventListener('submit', ...) and inside a React onSubmit handler, whatever the function returns is discarded, so the browser submits normally. Call event.preventDefault() in those handlers. The other common case is onsubmit="validate()" without the return keyword: the attribute returns undefined, which is not false.
Is return false deprecated?
Not formally. Returning false from an inline onsubmit attribute still cancels the submission in every browser, and that behaviour is specified. What is legacy is the inline handler itself: it mixes markup with logic, cannot use modules, and breaks under a strict Content-Security-Policy. New code should use addEventListener plus preventDefault().
What is the difference between preventDefault and stopPropagation?
preventDefault() cancels the browser's default action for the event, which for a submit event means the navigation and the POST. stopPropagation() stops the event from bubbling to listeners on parent elements, such as analytics or validation wrappers on document. They are independent: you can cancel the submit and still let other handlers see the event.
Does preventDefault() skip HTML5 validation?
No. The submit event fires only after constraint validation passes, so required, pattern and type=email checks have already run when your handler executes. The exceptions are a form with novalidate, a submit button with formnovalidate, or a call to form.submit(), which fires no submit event at all.
Why does the page reload when my submit handler throws an error?
Because preventDefault() never ran. If validation or an await statement above it throws, the exception aborts the handler and the browser carries on with the native submission, so the page navigates and unsent data is lost. Put preventDefault() on the first line of the handler, before any code that can fail.
References: MDN: Event.preventDefault() and MDN: HTMLFormElement submit event.
Need an endpoint that accepts the POST once you stop cancelling it? splitforms takes a standard form POST, filters spam, stores every submission and emails you. Related reading: HTML form not submitting and validating a form with JavaScript.