Skip to content

Instantly share code, notes, and snippets.

@rstacruz
Last active May 26, 2020 04:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rstacruz/12da792c84e7e12df308f7051cce317f to your computer and use it in GitHub Desktop.
Save rstacruz/12da792c84e7e12df308f7051cce317f to your computer and use it in GitHub Desktop.

A few reasons why ID's should be avoided:

ID’s are not unique

It is possible to have more than 1 ID in a page, and this is not going to cause any issues in any tools. It will be okay with CSS:

/* This will apply to all with elements with this ID ⚠️ */
#id-selector { color: blue; }

Same with JavaScript:

// Will return multiple results ⚠️
document.querySelectorAll('#id-selector')

// Will return the first result, even if you use ID or class ✓
document.querySelector('.my-class')

ID’s pollute the JavaScript global scope

If you have an element with an ID, that ID will be a global variable stored in window. For instance, if you had this element in the DOM:

<form id='submitform'>...</form>

You will have this a global variable for it:

submitform.submit()
console.log(window.submitform)

Screenshot of web inspector showing the behaviour above

ID’s bring confusion to CSS specificity

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:

// highlight-range{6-8}
<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. Unfortunately, there might be cases where this won't work: in our example, it might look like invisible text.

// highlight-range{4}
.register-actions a {
  display: block;
  background: $green;
  color: white; /* ??? */
}

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.

// highlight-range{6}
/*
 * This rule takes precedence over .register-actions a
 * due to specificity rules.
 */
#register-page a {
  color: $green;
}

The ID rule (#register-page a) will override the class rule (.register-actions a), regardless of the ordering of the CSS in the document. This can be fixed by avoiding ID's and styling .register-page a instead.

A demo of the above: https://codepen.io/rstacruz/pen/xxGBped

ID’s in JavaScript can be deceiving

Behaviour is sometimes attached to ID selectors that pick out a specific element.

// highlight-range{1}
let form = document.getElementById('submit-form');

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

This can have a few issues:

  • Some projects use ID selectors (#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—eg, if you want the same behaviour without the styling.

  • It can be a bit awkward to test, since you’ll probably be making a mock <div id='submit-form'> for it.

If ID’s are used in this way, consider attaching JavaScript behaviours to data attribute. In the example above, there’s no easy way to attach this same generic behaviour to multiple forms.

Instead of querying by ID, we can refactor the above to target a data attribute:

<form data-disable-on-submit>...</form>

This way, the data-disable-on-submit attribute can be added to any form and for it to pick up the behaviour.

There are alternatives to ID’s in tests

There are other ways to refer to elements in tests rather than ID's. One common convention in JavaScript testing is to prefer to choose tests in this order:

  1. Label text for form fields
  2. Placeholder text for form fields
  3. By the [role] attribute, since this is what's used in the accessibility tree
  4. By text
  5. By the [alt] text
  6. By the [title] text
  7. By the [data-testid] attribute

More on this is in here written in this article below. This isn't Capybara, but given that Capybara hasn't published documentation like this (as far as I know), this is the closest substitute we have.

A more opinionated piece is here explaining the above, as well as the rationale behind [data-testid].

🍰 So what are ID’s useful for?

Permalinks

ID’s can be used as text anchors for permalinks.

<h1 id='introduction'>Introduction</h1>
.
.
.
<a href='#introduction'>Go to introduction</a>

For and aria-labeledby

It's necessary for attributes like for and aria-labeledby.

<input type='checkbox' id='accept-terms'>
<label for='accept-terms'>Accept terms</label>
<button aria-labeledby='my-label'>
  <span id='my-label'>Save</span>
</button>

References

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment