Why is using onClick() in HTML a bad practice?

I have heard many times that using JavaScript events, such as onClick(), in HTML is a bad practice, because it's not good for semantics. I would like to know what the downsides are and how to fix the following code?

<a href="#" onclick="popup('/map/', 300, 300, 'map'); return false;">link</a>

You're probably talking about unobtrusive Javascript, which would look like this:

<a href="#" id="someLink">link</a>

with the logic in a central javascript file looking something like this:

    popup('/map/', 300, 300, 'map'); 
    return false;

The advantages are

  • behaviour (Javascript) is separated from presentation (HTML)
  • no mixing of languages
  • you're using a javascript framework like jQuery that can handle most cross-browser issues for you
  • You can add behaviour to a lot of HTML elements at once without code duplication

If you are using jQuery then:


 <a id="openMap" href="/map/">link</a>


$(document).ready(function() {
        popup('/map/', 300, 300, 'map');
        return false;

This has the benefit of still working without JS, or if the user middle clicks the link.

It also means that I could handle generic popups by rewriting again to:


 <a class="popup" href="/map/">link</a>


$(document).ready(function() {
        popup($(this).attr("href"), 300, 300, 'map');
        return false;

This would let you add a popup to any link by just giving it the popup class.

This idea could be extended even further like so:


 <a class="popup" data-width="300" data-height="300" href="/map/">link</a>


$(document).ready(function() {
        popup($(this).attr("href"), $(this).data('width'), $(this).data('height'), 'map');
        return false;

I can now use the same bit of code for lots of popups on my whole site without having to write loads of onclick stuff! Yay for reusability!

It also means that if later on I decide that popups are bad practice, (which they are!) and that I want to replace them with a lightbox style modal window, I can change:

popup($(this).attr("href"), $(this).data('width'), $(this).data('height'), 'map');


myAmazingModalWindow($(this).attr("href"), $(this).data('width'), $(this).data('height'), 'map');

and all my popups on my whole site are now working totally differently. I could even do feature detection to decide what to do on a popup, or store a users preference to allow them or not. With the inline onclick, this requires a huge copy and pasting effort.

With very large JavaScript applications, programmers are using more encapsulation of code to avoid polluting the global scope. And to make a function available to the onClick action in an HTML element, it has to be in the global scope.

You may have seen JS files that look like this...

    ...[some code]

These are Immediately Invoked Function Expressions (IIFEs) and any function declared within them will only exist within their internal scope.

If you declare function doSomething(){} within an IIFE, then make doSomething() an element's onClick action in your HTML page, you'll get an error.

If, on the other hand, you create an eventListener for that element within that IIFE and call doSomething() when the listener detects a click event, you're good because the listener and doSomething() share the IIFE's scope.

For little web apps with a minimal amount of code, it doesn't matter. But if you aspire to write large, maintainable codebases, onclick="" is a habit that you should work to avoid.

It's not good for several reasons:

  • it mixes code and markup
  • code written this way goes through eval
  • and runs in the global scope

The simplest thing would be to add a name attribute to your <a> element, then you could do:

document.myelement.onclick = function() {
    window.popup('/map/', 300, 300, 'map');
    return false;

although modern best practise would be to use an id instead of a name, and use addEventListener() instead of using onclick since that allows you to bind multiple functions to a single event.

There are a few reasons:

  1. I find it aids maintenence to separate markup, i.e. the HTML and client-side scripts. For example, jQuery makes it easy to add event handlers programatically.

  2. The example you give would be broken in any user agent that doesn't support javascript, or has javascript turned off. The concept of progressive enhancement would encourage a simple hyperlink to /map/ for user agents without javascript, then adding a click handler prgramatically for user agents that support javascript.

For example:


<a id="example" href="/map/">link</a>



        popup('/map/', 300, 300, 'map');
        return false;


It's a new paradigm called "Unobtrusive JavaScript". The current "web standard" says to separate functionality and presentation.

It's not really a "bad practice", it's just that most new standards want you to use event listeners instead of in-lining JavaScript.

Also, this may just be a personal thing, but I think it's much easier to read when you use event listeners, especially if you have more than 1 JavaScript statement you want to run.


Unobtrusive JavaScript approach was good in the PAST - especially events handler bind in HTML was considered as bad practice (mainly because onclick events run in the global scope and may cause unexpected error what was mention by YiddishNinja)


Currently it seems that this approach is a little outdated and needs some update. If someone want to be professional frontend developper and write large and complicated apps then he need to use frameworks like Angular, Vue.js, etc... However that frameworks usually use (or allow to use) HTML-templates where event handlers are bind in html-template code directly and this is very handy, clear and effective - e.g. in angular template usually people write:

<button (click)="someAction()">Click Me</button> 

In raw js/html the equivalent of this will be

<button onclick="someAction()">Click Me</button>

The difference is that in raw js onclick event is run in the global scope - but the frameworks provide encapsulation.

So where is the problem?

The problem is when novice programmer who always heard that html-onclick is bad and who always use btn.addEventListener("onclick", ... ) wants to use some framework with templates (addEventListener also have drawbacks - if we update DOM in dynamic way using innerHTML= (which is pretty fast) - then we loose events handlers bind in that way). Then he will face something like bad-habits or wrong-approach to framework usage - and he will use framework in very bad way - because he will focus mainly on js-part and no on template-part (and produce unclear and hard to maintain code). To change this habits he will loose a lot of time (and probably he will need some luck and teacher).

So in my opinion, based on experience with my students, better would be for them if they use html-handlers-bind at the beginning. As I say it is true that handlers are call in global scope but a this stage students usually create small applications which are easy to control. To write bigger applications they choose some frameworks.

So what to do?

We can UPDATE the Unobtrusive JavaScript approach and allow bind event handlers (eventually with simple parameters) in html (but only bind handler - not put logic into onclick like in OP quesiton). So in my opinion in raw js/html this should be allowed

<button onclick="someAction(3)">Click Me</button>


function popup(num,str,event) {
   let re=new RegExp(str);  
   // ... 
   console.log("link was clicked");
<a href="https://example.com" onclick="popup(300,'map',event)">link</a>

But below examples should NOT be allowed

<button onclick="console.log('xx'); someAction(); return true">Click Me</button>

<a href="#" onclick="popup('/map/', 300, 300, 'map'); return false;">link</a>

The reality changes, our point of view should too

Your question will trigger discussion I suppose. The general idea is that it's good to separate behavior and structure. Furthermore, afaik, an inline click handler has to be evalled to 'become' a real javascript function. And it's pretty old fashioned, allbeit that that's a pretty shaky argument. Ah, well, read some about it @quirksmode.org

  • onclick events run in the global scope and may cause unexpected error.
  • Adding onclick events to many DOM elements will slow down the
    performance and efficiency.

Two more reasons not to use inline handlers:

They can require tedious quote escaping issues

Given an arbitrary string, if you want to be able to construct an inline handler that calls a function with that string, for the general solution, you'll have to escape the attribute delimiters (with the associated HTML entity), and you'll have to escape the delimiter used for the string inside the attribute, like the following:

const str = prompt('What string to display on click?', 'foo\'"bar');
const escapedStr = str
  // since the attribute value is going to be using " delimiters,
  // replace "s with their corresponding HTML entity:
  .replace(/"/g, '&quot;')
  // since the string literal inside the attribute is going to delimited with 's,
  // escape 's:
  .replace(/'/g, "\\'");
  '<button onclick="alert(\'' + escapedStr + '\')">click</button>'

That's incredibly ugly. From the above example, if you didn't replace the 's, a SyntaxError would result, because alert('foo'"bar') is not valid syntax. If you didn't replace the "s, then the browser would interpret it as an end to the onclick attribute (delimited with "s above), which would also be incorrect.

If one habitually uses inline handlers, one would have to make sure to remember do something similar to the above (and do it right) every time, which is tedious and hard to understand at a glance. Better to avoid inline handlers entirely so that the arbitrary string can be used in a simple closure:

const str = prompt('What string to display on click?', 'foo\'"bar');
const button = document.body.appendChild(document.createElement('button'));
button.textContent = 'click';
button.onclick = () => alert(str);

Isn't that so much nicer?

The scope chain of an inline handler is extremely peculiar

What do you think the following code will log?

let disabled = true;
  <button onclick="console.log(disabled);">click</button>

Try it, run the snippet. It's probably not what you were expecting. Why does it produce what it does? Because inline handlers run inside with blocks. The above code is inside three with blocks: one for the document, one for the <form>, and one for the <button>:

let disabled = true;
  <button onclick="console.log(disabled);">click</button>

enter image description here

Since disabled is a property of the button, referencing disabled inside the inline handler refers to the button's property, not the outer disabled variable. This is quite counter-intuitive. with has many problems: it can be the source of confusing bugs and significantly slows down code. It isn't even permitted at all in strict mode. But with inline handlers, you're forced to run the code through withs - and not just through one with, but through multiple nested withs. It's crazy.

with should never be used in code. Because inline handlers implicitly require with along with all its confusing behavior, inline handlers should be avoided as well.