When to use the id attribute

When to use the id attribute

The id attribute is arguably one of the most popular attributes used in HTML, second only to the class attribute. It serves a lot of purposes, which means it can easily be misunderstood or misused. Let's take a look at what it's really for, what it's not for, and what alternatives might be better.

When to use the id attribute

Associate a button with a form

A form's submit button doesn't need to be nested inside the form element if the form element is assigned a unique id and the button has a form attribute with the same value.

<form id="nameForm">
  <input name="name" />
  <input name="surname" />
</form>

...

<button type="submit" form="nameForm">
  Submit the form
</button>

Associate a label with an input

Every input element needs a companion label input. It's not enough to just render text near the input though, the label must be properly associated with the input so that browsers know they're associated. This doesn't just help screen readers, the label text is part of the focus area for the input so users expect to be able to click on the label text to focus on the input. This UX is provided for free as the input is rendered inside the label or if the input has a unique id and the label has a for attribute with the same value.

<label for="givenNameField">
  Given name
</label>
<input id="givenNameField" type="text" />

Indicate an element should be the accessible name of another

Using aria-labeledby on an element allows you to indicate that another element's contents should be considered the accessible name for the element. Screen readers use this when announcing an element to the user. This is similar to aria-label but aria-labeledby takes the highest precedence and should be preferred over aria-label so long as the appropriate text is rendered somewhere on the page.

The value of aria-labeledby can be a space-delimited list of IDs, so it's possible for the label to be comprised of multiple elements.

<h2 id="formTitle">Favorite color</h2>

<form aria-labeledby="formTitle">
  ...
</form>

Indicate an element should be the accessible description of another

Using aria-describedby on an element allows you to indicate that another element's contents describe the element. While it sounds similar to aria-labeledby, aria-describedby is intended to be used for longer descriptions. This is similar to aria-description but aria-describedby takes the highest precedence and should be preferred over aria-label so long as the appropriate text is rendered somewhere on the page.

The value of aria-describedby can be a space-delimited list of IDs, so the description can be comprised of multiple elements.

<p id="formDescription">
  Let us know what your favorite color is so we can send you some sweet swag!
</p>

<form aria-describedby="formDescription">
  ...
</form>

Indicate an element contains detailed information about another

Using aria-details on an element allows you to indicate that another element's contents contain additional details about it. This is very similar to aria-describedby the difference being that aria-details is intended to be used when the describing element contains more complex and structured content, such as lists, images, links, etc., and aria-describedby is intended to be used with a text-only description.

The value of aria-details can be a space-delimited list of IDs, so the detailed description can be comprised of multiple elements.

<button type="submit" aria-details="finePrint">
  Purchase
</button>

<div id="finePrint">
  <p>Purchasing this means that lorem ipsum whatever...</p>
  <h3>Known side effects</h3>
  <ul>
    <li>...</li>
    <li>...</li>
  </ul>
  <nav aria-label="More information">
    <ul>
      <li><a>...</a></li>
      <li><a>...</a></li>
      <li><a>...</a></li>
    </ul>
  </nav>
</div>

Indicate that interacting with an element affects another

In dynamic web pages, it's common to have buttons with cause-and-effect behavior that affects another element on the page. Clicking a button may make the content change somewhere else on the page, or maybe it makes a dialog appear. You can formalize this relationship by using aria-controls on the trigger element so that screen readers are able to relay this intent to the end user.

<button type="button" aria-controls="infoDialog" onclick="openDialog">
  Open dialog
</button>

<dialog id="infoDialog">
  ...
</dialog>

Most custom form designs don't rely on the browser's native error handling. Instead, input error messages are rendered somewhere (hopefully near) the input it's related to. aria-errormessage should be used on the associated input element so that screen readers can properly relay this information to the user. Typically this should be used alongside aria-invalid.

<label for="nameInput">Name</label>
<input
  type="text"
  id="nameInput"
  aria-invalid="true"
  aria-errormessage="nameError"
/>
<p id="nameError">
  Sorry! We don't support names with emoji characters 😭
</p>

Suggest an alternative navigation flow through the page

Sometimes content on the page isn't in sequential order. Sometimes there is no real sequential order and multiple sections on the page could be viewed in any order. You can suggest an alternative flow through a page by using aria-flowto on an element to suggest one or more other elements that could be viewed next. Screen readers that support this won't force the flow onto the user, instead, they will suggest an alternative flow to the user and let the user decide. Support for aria-flowto isn't great as of right now, but it can be worth the minor effort of providing a great user experience.

<h1 aria-flowto="description details reviews">
  Super awesome product
</h1>

<section id="description">
  <h2>Description</h2>
  ...
</section>

<section id="details">
  <h2>More details</h2>
  ...
</section>

<section id="Reviews">
  <h2>Reviews</h2>
  ...
</section>

Indicate that an element should be treated as the parent of another

A parent-child relationship is an important part of the DOM hierarchy. Special design considerations and sometimes limited having limited control of the final DOM hierarchy can mean that you can't always render an element within the element where it should be placed. If this is the case and you need to modify the accessibility tree of a page you can override the interpreted hierarchy by setting aria-owns on the parent element to indicate that another element should be viewed as a direct child of that element. This should only be used when you don't have access to modify the DOM structure as it's preferred you just restructure the DOM if possible.

<dl aria-owns="additionalTerm additionalDescription">
  <dt>Term</dt>
  <dd>Value</dd>
</dl>

<dt id="additionalTerm">Another term</dt>
<dd id="additionalDescription">Another value</dd>

This example is the equivalent accessibly to:

<dl>
  <dt>Term</dt>
  <dd>Value</dd>
  <dt>Another term</dt>
  <dd>Another value</dd>
</dl>

Allow users to navigate to a specific place on a page

Anchor links allow you to link to a specific location within a page. This can be done by setting the fragment identifier in the URL to match the id of the element you want to link to. Since the fragment identifier is exposed to the end user through the URL, it's a good idea to ensure the target element's id is readable and intuitive. This can be useful in building a table of contents and allowing users to share a specific part of the page.

When not to use the id attribute

With so many good use cases for the id attribute, it's a good idea to avoid using it if doesn't solve one of the use cases listed above to avoid confusing other engineers and potentially creating a worse experience for the user.

Avoid using id as a CSS selector

The id can also be used as a CSS selector, like so:

#header {
  background: rebeccapurple;
}

While this is valid, it can create some issues down the road that should be considered.

High Specificity

It turns out that the "C" for "cascading" in CSS isn't the final decider regarding what rules apply to what elements. Specificity is also taken into account, and as it turns out id selectors have the highest specificity. This means that any rules declared using an id selector will have a higher priority over rules declared with other selectors, even if those rules are declared later on.

Low reusability

Since an id must be unique to each page; any style rules applied using an id selector can only apply to a single element (and its children) at a time. This doesn't promote reusability as those styles are limited to a single use case.

What to use instead

Prefer using class names or data attributes as CSS selectors.

Avoid using id as a JavaScript selector

It's possible to select an element in JavaScript by referencing the element's id like so:

document.getElementById('elementId')

While this is valid, it discourages reusability as the JavaScript code is forced to be tightly coupled to the DOM structure and it's not possible to re-use the code on multiple elements at the same time if necessary.

What to use instead

Prefer using unique data attributes as JavaScript selectors instead. This provides more flexibility and improves code readability as the data attribute can have a clear and descriptive name.

document.querySelectorAll('[data-vue-app]')
document.querySelector('[data-vue-app="myAppName"]')

Avoid using id as a selector in tests

Quality tests should avoid being tightly coupled to the HTML and DOM structure of the page; they should instead focus on testing the experience from the eyes of the user. It's possible for someone to come along, see an id on an element, and not see any accessibility advantage to having the id and remove it. This could break tests they didn't know about despite not making a change that affects the end-user experience.

What to use instead

Most testing frameworks have support for some type of "test id" such as data-testid. Refer to your testing library's best practices, but use a unique data attribute as a selector to improve code readability.