JavaScript void 0: What It Means and When to Use It
voidis a JavaScript operator that evaluates whatever follows it and always returnsundefined, sovoid 0,void(0)andvoid "anything"all produce the same value. In an old-style link,href="javascript:void(0)"pairs that operator with thejavascript:URL scheme: the browser runs the code, and because the result isundefinedrather than a string, it leaves the page alone instead of replacing it. That is the entire trick. If you are not a developer and you are here because a link on some site does nothing when you click it, skip ahead to “The Link Says javascript:void(0) and Nothing Happens” near the end. Today a<button>handles actions andpreventDefault()handles links, butvoiditself is alive and well in minified bundles and in modern code that needs to throw a return value away.
javascript:void(0) Is Two Separate Things
You are looking at one string, but it is two technologies stacked on each other, governed by two different specifications.
javascript: is a URL scheme. It belongs to the browser, in the same family as https: and mailto:. MDN lists it as usable in the href of <a> and <area>, the action of a <form>, the src of an <iframe>, and in window.location.
void is a JavaScript operator. It belongs to the language, specified in ECMA-262 as section 13.5.2, “The void Operator”, under Unary Operators. It has no idea links exist.
Keep the two apart and the whole topic falls into place. Every question you might have about this string belongs to exactly one side: precedence and side effects are language questions, “why doesn’t the page navigate” is a browser question.
Start with the operator, because it is the smaller half. void evaluates the expression that follows it and then returns undefined. Always undefined, whatever the expression was.
console.log(void 0);
console.log(void(0));
console.log(void "anything");
console.log(void { total: 42 });
undefined
undefined
undefined
undefined
Nothing else. MDN states it plainly: “The void operator is often used merely to obtain the undefined primitive value, usually using void(0) (which is equivalent to void 0).”
The parentheses in void(0) mislead people into reading it as a function call. It is not one. void is a prefix unary operator like typeof or !, so void(0) is void applied to the grouped expression (0), exactly as typeof("x") is typeof applied to ("x"). The 0 carries no meaning either. It is the shortest operand available.
The void Operator: Precedence, Side Effects, and Gotchas
Language side. No links in this section.
void sits at precedence 14 in MDN’s operator precedence table, sharing that level with prefix ++/--, !, ~, unary +/-, typeof, delete and await. That is above exponentiation at 13 and the multiplicative operators at 12, far above comparison and equality. High precedence means void claims its operand before almost anything else gets a turn, which produces the trap MDN documents on the void page itself:
console.log(void 2 === "2");
console.log(void (2 === "2"));
false
undefined
The first line parses as (void 2) === "2". void 2 is undefined, and undefined === "2" is false. The second line groups the comparison first and then discards its result. If you mean the second, write the parentheses.
Exponentiation is stricter about it. A precedence-14 unary operator, and MDN’s list names void explicitly, cannot appear immediately before the base of **. So void 2 ** 2 is a SyntaxError rather than a silent misparse. Firefox puts it as “unparenthesized unary expression can’t appear on the left-hand side of **”; Chrome and Safari word the same complaint differently. Both disambiguations are legal:
console.log((void 2) ** 2);
console.log(void (2 ** 2));
NaN
undefined
The operand is evaluated, not skipped. void discards the value, not the work:
let count = 0;
function bump() {
count += 1;
return count;
}
console.log(void bump());
console.log(count);
undefined
1
That has one consequence people trip over, because typeof sits at the same precedence level and behaves differently:
console.log(typeof missingThing);
try {
void missingThing;
} catch (err) {
console.log(err.name);
}
undefined
ReferenceError
typeof is the exception in this group. MDN notes that “typeof works with undeclared identifiers, returning "undefined" instead of throwing an error.” void gets no such treatment. It reads its operand like any other expression, so an undeclared identifier throws.
Precedence is a table to consult rather than memorise. Basic operators, maths works through the rest of them.
Why the javascript: URL Scheme Needs void at All
Browser side. Now the other half.
When you click a link whose href starts with javascript:, the browser evaluates the rest of the URL as script and then inspects the completion value, the value that code produced. What happens next depends entirely on the type of that value.
MDN gives the rule: “When a browser follows a javascript: URI, it evaluates the code in the URI and then replaces the contents of the page with the returned value, unless the returned value is undefined.”
The HTML Standard is more precise about the mechanism. Its algorithm for evaluating a javascript: URL returns null when the completion value is not a String. When the completion value is a String, the browser synthesises a response carrying Content-Type: text/html;charset=utf-8 and builds a whole new document from it. When the algorithm returns null there is no new document, so no navigation occurs and the current page is left in place.
So the pattern is not folklore. href="javascript:document.title" evaluates to a string, and the browser hands you a fresh HTML document containing that title and nothing else; the page you were reading is gone. href="javascript:void(document.title)" reads the same property and produces undefined, so nothing happens. Same code, different completion value, opposite outcome.
That is the job void was doing in href="javascript:void(0)": guaranteeing a non-String completion value whatever the handler beside it returned. void 0 is the shortest expression that guarantees it.
href="javascript:;" is the same trick by another route. An empty statement produces no completion value at all, which is also not a String, so the browser stays put.
Bookmarklets are the clearest remaining legitimate use of javascript: URLs, and void matters there for exactly this reason: a bookmarklet whose last expression happens to evaluate to a string will replace the page it was meant to act on.
Why void 0 Fills Your Minified Bundle
Plenty of people meet void 0 for the first time in a stack trace or a bundle, nowhere near an anchor tag. That usage has nothing to do with links.
MDN, on the undefined page: “The void operator can also be used to produce the undefined value. This is very commonly seen in minified code because void 0 is 3 bytes shorter and cannot be overridden.”
Two reasons, and the second is the interesting one.
undefined is nine characters. void 0 is six. Multiply by every occurrence in a large bundle and it pays for itself.
The shadowing story takes longer. undefined is a property of the global object, and in all non-legacy browsers it is non-configurable and non-writable, so the global one cannot be reassigned. But undefined is not a reserved word. It is still a legal identifier in any scope other than the global scope, and MDN’s own example shows the result:
(function () {
const undefined = "foo";
console.log(undefined, typeof undefined);
console.log(void 0, typeof void 0);
})();
foo string
undefined undefined
Inside that function, undefined is a string. void 0 is untouched, because it is an operator applied to a numeric literal and there is no name for anyone to rebind. Nobody writes const undefined = "foo" on purpose, but jQuery-era libraries wrapped themselves in (function (window, document, undefined) { ... })(window, document) on purpose, and one stray third argument gives that identifier some other value. A minifier cannot assume the name undefined still holds the value undefined, and a transform that can never be wrong is worth more than one that is usually right.
Turning undefined into void 0 is not something you ask Terser for; it is ordinary minification, and the typeofs compress option, on by default, goes further and rewrites typeof foo == "undefined" into foo === void 0. The option carrying the unsafe_ prefix runs the other way: unsafe_undefined, default false, is documented as “substitute void 0 if there is a variable named undefined in scope (variable name will be mangled, typically reduced to a single character)”, trading those six bytes back for a one-character name. The prefix is the warning, because nothing guarantees that variable holds undefined.
Where void Is Still the Right Tool
Uses of the operator, per the split in the first section. No javascript: URL appears anywhere below.
Discarding a return value from a concise arrow body. An arrow function written without braces returns whatever its body evaluates to, and sometimes that leaks:
const log = [];
function doSomething() {
log.push("ran");
return "a value nobody asked for";
}
const leaky = () => doSomething();
const tight = () => void doSomething();
console.log(leaky());
console.log(tight());
a value nobody asked for
undefined
MDN’s version is an event handler: checkbox.onclick = () => void doSomething(); guarantees the handler returns undefined. The same shape matters in React, where useEffect’s setup function may optionally return a cleanup function; a concise arrow body that returns something else is putting a value into the slot reserved for cleanup. void keeps the arrow concise and the return value undefined. Arrow functions, the basics covers the concise-body rule itself.
Marking a promise as deliberately unawaited. typescript-eslint’s no-floating-promises flags promises you neither await nor handle. Its ignoreVoid option defaults to true, and the rule documents the pattern: “Placing the void operator in front of a Promise can be a convenient way to explicitly mark that Promise as intentionally not awaited.”
async function saveDraft() {
return "saved";
}
function handleClick() {
void saveDraft();
console.log("handler finished");
}
handleClick();
console.log("next line");
handler finished
next line
As an IIFE prefix. An IIFE, an immediately invoked function expression, is a function defined and called in the same breath. A statement that starts with the word function is parsed as a declaration, and a declaration cannot be invoked where it stands, so something has to mark it as an expression first. void function () { /* ... */ }(); forces that parse the same way a leading ! or a wrapping ( does, and MDN prefers it: “Of all the unary operators, void offers the best semantic, because it clearly signals that the return value of the function invocation should be discarded.” Function expressions has the parsing rule behind it.
All three will be flagged if your config enables ESLint’s no-void, a rule you have to turn on yourself, whose stated aim is “to eliminate use of void operator”. Its allowAsStatement option, default false, exists for exactly the statement-position cases above: enable it and void doSomething(); on its own line passes, while const x = void 0; still fails.
What to Use Instead of href=“javascript:void(0)”
Back to the URL scheme. The mechanism works, and it is still the wrong tool.
MDN’s accessibility guidance on <a> names the pattern by name: “Anchor elements are often abused as fake buttons by setting their href to # or javascript:void(0) to prevent the page from refreshing… These bogus href values cause unexpected behavior when copying/dragging links, opening links in a new tab/window, bookmarking, or when JavaScript is loading, errors, or is disabled. They also convey incorrect semantics to assistive technologies, like screen readers. Use a <button> instead.”
The semantics are concrete rather than a matter of taste. Per W3C’s ARIA in HTML, an a element with an href has the implicit role link. Assistive technology announces a link, and a link promises navigation. javascript:void(0) puts an action behind that promise.
For an action, use a button and style it however the design requires:
<button type="button" class="link-style">Show more</button>
It is focusable, it is announced as a button, and it activates on both Enter and Space with no key handling from you.
For something that really is navigation but is enhanced by script, put the real destination in the href and cancel the default in the handler:
<a href="/reports/2026-q1" data-enhance>Q1 report</a>
function onClick(event) {
if (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
return;
}
event.preventDefault();
loadReport(event.currentTarget.pathname);
}
for (const link of document.querySelectorAll("[data-enhance]")) {
link.addEventListener("click", onClick);
}
Middle-click, ctrl-click, copy-link-address and bookmarking all keep working, and if the script fails to load the link still goes to the report.
Two other forces push the same way. Content Security Policy is one: MDN notes that javascript: URLs can be blocked by the script-src directive, so under a strict policy the click does nothing on the page while the console carries a violation naming the directive, which is the tell that separates it from a bug in your own handler. MDN describes the scheme as discouraged for the same reasons, that it can lead to execution of arbitrary code and reduces accessibility.
React is the other. React 16.9 shipped a “Deprecating javascript: URLs” change: the pattern kept working but logged a warning, and the release post said “In a future major release, React will throw an error if it encounters a javascript: URL.” The React 19 upgrade guide’s changelog lists, under other breaking changes, “react-dom: Error for javascript URLs in src and href (#26507)”.
”The Link Says javascript:void(0) and Nothing Happens”
If you arrived here after hovering a link on a site that will not work, start with what this is not. javascript:void(0) is not an error message, not an error code, and JavaScript never produces a message containing that text. It is the link’s address. You are seeing it in the status bar at the bottom of the window, in a tooltip, or because you copied the link and pasted it somewhere.
What it tells you is narrow but real: the page’s author intended a script to handle the click, and the link itself deliberately goes nowhere. When clicking does nothing, the script is what failed, not the link.
Worth checking on your side, starting with what you can do something about:
- JavaScript is turned off in the browser, or turned off for this site.
- A content blocker, privacy extension or corporate proxy is blocking the site’s scripts. Try the page with extensions disabled, or in a private window where they do not load.
- A stale cached bundle. A hard reload fetches the scripts again.
- An error earlier on the page stopped the script before it attached the click handler. The browser console shows it, usually in red.
That last one is the common case, and it is not yours to fix. Neither is a script blocked by the site’s own Content Security Policy, nor a bundle that shipped broken. If the console shows an error coming from the site’s own code, the site is broken for everyone with a setup like yours, and the repair belongs to whoever ships it. Reporting it with the console message attached does more than any setting you can change.
Quick Reference
| What you see | What it does | Use it? |
|---|---|---|
void 0, void(0), void expr | Evaluates the operand, returns undefined | Fine |
href="javascript:void(0)" | Completion value is not a String, so the browser does not navigate | Avoid |
href="javascript:;" | Same result, via an empty completion value | Avoid |
href="#" | Navigates to the top of the current page | Only for a real back-to-top link |
<a> with no href | Implicit ARIA role generic, out of the tab order | Not a fix |
<button type="button"> | A real button: focusable, Enter and Space activate it | Correct for actions |
event.preventDefault() | Stops the browser following a real href | Correct for script-enhanced links |
() => void fn() | Concise arrow body that always returns undefined | Correct when a return value would be misread |
void somePromise() | Marks a promise as intentionally unawaited for no-floating-promises | Correct, with a .catch() for rejections |
Frequently asked questions
What does javascript:void(0) mean?
javascript: URL scheme, which tells the browser to run the rest of the URL as code, and the void operator, which evaluates 0 and returns undefined. Because the resulting value is not a string, the browser leaves the current page in place instead of replacing it. The net effect is a link that goes nowhere, leaving a click handler to do the actual work.Is void 0 the same as undefined?
void always returns undefined regardless of its operand, so void 0, void(0) and void "anything" all evaluate to undefined. The difference is that undefined is an identifier that can be shadowed in any non-global scope, while void 0 has no name for anyone to rebind.Why is void 0 everywhere in minified JavaScript?
void 0 is 3 bytes shorter than undefined and cannot be overridden. In all non-legacy browsers undefined is a non-configurable, non-writable property of the global object, but it is not a reserved word, so it can still be used as a variable name inside any function. That makes void 0 the safer of the two for generated code.Should I use href="#" instead of javascript:void(0)?
href="#" navigates to the top of the current page and adds a history entry, which is a different bug rather than a fix. MDN's accessibility guidance on <a> names both # and javascript:void(0) as fake-button patterns that convey incorrect semantics to assistive technologies. Use <button type="button"> for actions, and a real href plus event.preventDefault() for links that script enhances.How do I stop a link from navigating without javascript:void(0)?
event.preventDefault() for plain left-clicks only, letting clicks with a modifier key or a non-primary button fall through to the browser, and keep a real URL in the href. Middle-click, copy-link-address and open-in-new-tab all keep working, and the page still functions if the script fails to load. If the element performs an action rather than navigating, it should be a <button type="button">, and then there is nothing to cancel.