Avoid HTML ID's
ID's are not unique. It's possible to have more than one of the same ID in a page.
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:
- ID’s are not unique.
- ID’s pollute the JavaScript global scope.
- ID’s break specificity.
- ID’s can cause confusion in tests.
💣 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?
<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]
💣 ID’s pollute the JavaScript global scope. If an element has an ID, that ID will be a global variable stored in window
.
<form id="submitform">...</form>
submitform.submit()
console.log(window.submitform)
submitform
is automatically available in JavaScript.I came across this poking around Firefox’s Developer Tools. The JavaScript console shows this happening by default.

The DOM element is available in the global scope as window.drawer
.
💣 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>
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.

.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.
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.
💣 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.
💣 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.