1 December 2021

Avoid HTML ID's

ID's are not unique. It's possible to have more than one of the same ID in a page.

Rico Sta. Cruz @rstacruz

I often see ID attributes in HTML for styling elements, attaching JavaScript behaviours, and even using it in tests. Over time, I’ve found that ID’s may not be the best choice for any of these. I learned a few things:

Reason 1: ID’s are not unique

💣 It’s possible to have more than one of the same ID in a page. Try this: when querySelectorAll is used with an ID selector, will it return one result, or multiple results?

example.html
<div id="my-id-selector">One</div>
<div id="my-id-selector">Two</div>
<div id="my-id-selector">Three</div>
<div id="my-id-selector">Four</div>
document.querySelectorAll('#id-selector')
// → ???





// it will return multiple results:
// → [Node, Node, Node, Node]
Will it return 1 result or 4? Zoom in for the answer.

Reason 2: Global pollution

💣 ID’s pollute the JavaScript global scope. If an element has an ID, that ID will be a global variable stored in window.

example.html
<form id="submitform">...</form>
example.js
submitform.submit()
console.log(window.submitform)
Note how submitform is automatically available in JavaScript.

JavaScript console

I came across this poking around Firefox’s Developer Tools. The JavaScript console shows this happening by default.

Image

The DOM element is available in the global scope as window.drawer.

Reason 3: Breaks specificity

💣 ID’s can make CSS specificity confusing. When ID’s are used for styling, it will have a side effect of increasing the specificity of the CSS rules. Let’s start with a simple HTML document with ID’s and classes:

<body>
  <div id="register-page">
    <h1>Register now</h1>
    <p>
      If you already have an account,
      <a href="/login">log in</a> instead.
    </p>

    <div class="register-actions">
      <a href="#">Forgot your password?</a>
    </div>
  </div>
</body>

Example: trouble with styling text ⚠️

Some might try to style the forgot password link using .register-actions a. There might be cases where this won’t work: in our example, it might look like invisible text.

Image
failing_example.css
.register-actions a {
  display: block;
  background: var(--green);
  color: white; /* ??? */
}

The color: white rule doesn’t seem to work in this example. Try this in the Codepen demo.

How can it happen

This might happen if a CSS rule was previously defined for an ID selector, like one that might have been used to style the log in link. In essence, styling an ID is much like !important—it overrules any styles for class names.

#register-page a {
  color: green;
}

/* Now this won't work: */
.register-actions a {
  color: white;
}

The first selector (#register-page a) takes precedence over the second selector (.register-actions a) because it uses an ID. As a result, the color doesn’t turn to white.

Reason 4: Confusion in JavaScript

💣 ID’s in JavaScript can be deceiving. Behaviour is sometimes attached to ID selectors that pick out a specific element. Consider this example:

let form = document.getElementById('submit-form')

form.addEventListener('click', () => {
  let buttons = form.querySelectorAll('[type=submit]')
  ;[...buttons].forEach((b) => (b.disabled = true))
})

Here, getElementById is used to find a button to attach an event listener to.

Caveats to note

💣 Overloaded semantics — Some projects use ID selectors (eg, #submit-form) to signify style (CSS), behaviour (JavaScript) and semantics (Capybara tests). It’s overloaded, and it’d be hard to disconnect one from the other, such as if you want the same behaviour without the styling.

💣 Difficult to test — It can be a bit awkward to test, possibly needing a mock #submit-form element for it.

Thanks for reading! I'm Rico Sta Cruz, I write about web development and more. Subscribe to my newsletter!