Skip to main content

Aria Labelledby

Last time you learned aria-label, which lets you write an invisible name. But often the best name for something is already on the page. A dialog has a heading. A section has a title. A table row has the name of the item. It would be wasteful (and fragile) to type the same words again in an attribute.

aria-labelledby solves that. Instead of writing the name, you point to the text that already is the name, using its id. The name stays in one place, is visible to everyone, and is translated along with the page.


What You Will Learn​

  • What aria-labelledby does, and how it builds a name from other elements
  • How to name dialogs, sections, and groups from their headings
  • How to combine several pieces of text, and why order matters
  • How self-reference keeps a button's own text
  • Why hidden text still counts
  • Why ids must be unique and correct
  • How to choose between label, aria-label, and aria-labelledby

The Big Idea​

index.html
<!-- A dialog named by its heading -->
<div role="dialog" aria-labelledby="dialog-title">
<h2 id="dialog-title">Delete recipe?</h2>
<p>This cannot be undone.</p>
</div>

<!-- A section named by its heading -->
<section aria-labelledby="delivery-title">
<h2 id="delivery-title">Delivery</h2>
…
</section>

<!-- Self-reference: the button's own text, then the item name -->
<span id="rye-name">Rye bread</span>
<button id="rye-edit" aria-labelledby="rye-edit rye-name">Edit</button>

<!-- An input named from two pieces of text -->
<span id="amount-label">Amount</span>
<input type="number" aria-labelledby="amount-label amount-unit">
<span id="amount-unit">in US dollars</span>

Try each example in the lab. Untick an id, change the order, and add the mistyped id, and watch the name change.

How It Works​

The value of aria-labelledby is a list of ids, separated by spaces. The browser:

  1. Finds each element with that id, in the whole page.
  2. Takes the text of each one (including text of nested elements, and the alt of images).
  3. Joins them, in the order listed, with a space between.
  4. Uses the result as the name of the element that has the attribute.

Some important details:

  • It comes first. aria-labelledby is at the top of the naming order. It beats aria-label, native labels, and the element's own content. (See Accessible Names.)
  • It replaces the content. A button with aria-labelledby is named from the pointed-at text, not its own. If you want to keep its own text, include its own id in the list. This is called self-reference, and it is used in the "Edit Rye bread" example.
  • Order matters. "Edit Rye bread" and "Rye bread Edit" are different. Put the most important words first, and the visible text first.
  • Hidden text counts. An element hidden with display: none, visibility: hidden, the hidden attribute, or aria-hidden="true" is still used when you point to it. That is very handy: you can put a name in a hidden element, and use it for the label.
  • Missing ids are ignored, silently. A typo gives you no error, just no name.
  • Ids must be in the same page (the same document). You cannot point across an iframe or into another page.
  • It names, it does not describe. For extra help text, use aria-describedby instead. See aria-describedby.

When to Use It​

Name a dialog or region from its heading​

<div role="dialog" aria-labelledby="dlg-title">
<h2 id="dlg-title">Subscribe</h2>
…
</div>

Dialogs, regions, forms, and groups need names, and the heading is the natural one. This keeps the name and the heading in step: change the heading, and the name changes too.

<ul>
<li>
<span id="item-1">Rye bread</span>
<button id="edit-1" aria-labelledby="edit-1 item-1">Edit</button>
<button id="del-1" aria-labelledby="del-1 item-1">Delete</button>
</li>
</ul>

Every row says "Edit" and "Delete," but each button is named "Edit Rye bread," "Delete Rye bread," and so on. The visible text is first, so voice control still works (WCAG 2.5.3).

Name something from several pieces​

A field with a prefix or a unit, a card with a title and a price, a progress bar with a heading: list all the pieces, in reading order.

Name a widget from text that is hidden on purpose​

If an icon-only control has hidden text in the page, point to it. That gives you a name that translates like normal text.

When Not to Use It​

SituationBetter choice
A form field and a visible label next to itlabel with for. It also makes the click area bigger.
An icon-only button with no text anywherearia-label, or visually hidden text
The name is just the element's own textNothing. Content names it already.
You want to explain, not namearia-describedby
A plain div or span with no roleGive it a role, or restructure. A name there is not reliably announced.
An element that has both a label and aria-labelledbyChoose one. aria-labelledby overrides the label.

Choosing Between the Three​

Where the name comes fromVisible?Translates with the page?
labelA connected label elementYesYes
aria-labelledbyOther text on the page, pointed to by idUsually, or hidden on purposeYes (it is real text)
aria-labelA string you write in the attributeNoMaybe not, so test

A simple rule: visible text first (label, or the content). If the text is somewhere else on the page, use aria-labelledby. If there is no text at all, use aria-label.

Unique and Correct Ids​

aria-labelledby only works if the ids are right. Two things go wrong most often:

  1. A typo or a missing element. The reference is ignored, and nothing tells you.
  2. The same id used twice. Browsers return the first match, so every copy of a component points to the first heading. This happens a lot when a card or a dialog is used many times.

Try the lab below. It reads the real ids from the page, the way assistive technology does.

index.html
<!-- Wrong: the same id on every card -->
<h3 id="title">Sourdough</h3>
<a href="/a" id="l1" aria-labelledby="l1 title">Read more</a>
<h3 id="title">Rye</h3>
<a href="/b" id="l2" aria-labelledby="l2 title">Read more</a>

<!-- Right: a unique id for each card -->
<h3 id="title-1">Sourdough</h3>
<a href="/a" id="link-1" aria-labelledby="link-1 title-1">Read more</a>
<h3 id="title-2">Rye</h3>
<a href="/b" id="link-2" aria-labelledby="link-2 title-2">Read more</a>

How to keep ids unique:

  • Build ids from a unique value: an item id, an index, or a counter ("title-" + item.id).
  • In a framework, use its built-in id helper (such as React's useId) instead of fixed ids inside components that repeat.
  • Never use spaces in an id. A space would split it into two ids.
  • Keep the referenced element in the page while the label is needed. If it is removed or re-created with a different id, the name is lost.

Check Your References​

The checker below finds the usual aria-labelledby problems: ids that do not exist, duplicate ids, referenced elements with no text, a name that loses the visible text, labels that conflict with aria-label or a visible label, and names on plain div or span elements.

http://127.0.0.1:5500/index.html

On a real site, you can also:

  1. Open DevTools, select the element, and look at the Accessibility panel. Many browsers show the name, and which element it came from.
  2. Change the referenced text, and check that the name changes too.
  3. Test with voice control: say "click" and the visible text of each button.
  4. Use a screen reader on dialogs and regions, and listen for the names.
  5. Run an automated checker, which will find missing and duplicate ids.

Good Habits​

  1. Prefer visible text, and point to it with aria-labelledby when it is elsewhere on the page.
  2. List the ids in a natural reading order, with the visible text first.
  3. Include the element's own id when you want to keep its own text.
  4. Use unique ids, built from something unique.
  5. Name dialogs, regions, and forms from their headings.
  6. Do not mix label, aria-label, and aria-labelledby on the same element.
  7. Check every id in the list exists.
  8. Keep referenced text short, so the name is short.
  9. Test with a screen reader and voice control.

Accessibility Checklist​

  • Every id in every aria-labelledby exists, and is unique on the page.
  • The referenced elements have text.
  • The visible text of a control is included in its name, and comes first.
  • Dialogs, regions, and forms are named from their headings.
  • Repeated buttons and links have unique names.
  • Controls do not have a label, aria-label, and aria-labelledby all at once.
  • aria-labelledby is not used on plain div or span elements.
  • Names make sense on their own, and are not too long.
  • Referenced text is translated along with the page.
  • Names were checked in the Accessibility panel and with real assistive technology.

Security and Privacy Notes​

  • Hidden text can be read. Text you point to with aria-labelledby may be hidden on screen, but it is still in the page source, and a screen reader will read it aloud. Do not put private data or internal notes in it.
  • Do not use it to mislead. Naming a button "Cancel" from hidden text when it really confirms a payment is a deceptive pattern that harms people who cannot see the screen.
  • Be careful with user data. If a name is built from text that users can edit (such as a username or an item title), clean that text before putting it in the page, to prevent script injection.
  • Ids can be targeted. Page scripts and extensions can find elements by id. Do not rely on a hidden id to protect anything.
  • Do not use it to detect assistive technology. For example, checking whether hidden label text was read could reveal a disability.
  • Sensitive names: "Delete message from Sam" may be read aloud near other people. Think about this for private content, and about how names appear in shared screens and recordings.

Global Audience Notes​

  • It translates well. Because aria-labelledby points to real text on the page, translation tools translate the name along with everything else. That makes it a good choice for multilingual sites, and often safer than aria-label.
  • Order may need care in other languages. "Edit Rye bread" reads naturally in English. In some languages the item name or action may need to come in a different order. Build the order with translators in mind, and test it.
  • Right-to-left languages: the name is built in the order of the ids, which follows the reading logic and not the screen position. Check that mixed text (numbers, Latin words) reads sensibly.
  • Set lang on the page, and on any referenced text in another language, so the voice pronounces each part correctly.
  • Keep referenced text short and plain. It is easier to translate, and quicker to hear in a list of controls.
  • Voice control users speak the visible label in their own language. Keep the visible text first in the name.

Common Mistakes to Avoid​

  1. A typo in an id, so nothing is named.
  2. The same id used on several elements.
  3. Listing ids in the wrong order.
  4. Forgetting the element's own id, and losing its visible text.
  5. Using it together with aria-label or a visible label.
  6. Pointing to an element that is empty, or removed by script.
  7. aria-labelledby on a plain div or span.
  8. Using it to describe things, instead of aria-describedby.
  9. Very long names built from many pieces.
  10. Putting private data in hidden text.
  11. Using ids with spaces.
  12. Never checking the result in the Accessibility panel.

Practice Time​

Try these in your browser and your editor:

  1. Use the name builder lab with every example, and write down the final name for each.
  2. Change the order of the ids in the "Edit" example, and say which order is best, and why.
  3. Add the mistyped id, and explain why nothing breaks, but the name is wrong.
  4. In the duplicate id lab, fix the problem, and explain how.
  5. Run the checker on the messy page, fix every problem, and run it again.
  6. Name a dialog from its heading, and check it in the Accessibility panel.
  7. Build a list of three rows, each with "Edit" and "Delete" buttons that include the item name.
  8. Hide a text with display: none, and use it as a name with aria-labelledby.
  9. Replace an aria-label in a project with aria-labelledby, using text that is already visible.
  10. Bonus: in a component that repeats, generate a unique id for each copy, and test with three copies.

Quick Recap​

Click a question to check your answer.

1. What does aria-labelledby do?

It names an element from the text of one or more other elements on the page, found by their ids.

2. What does the order of ids do?

The name is built in the order the ids are listed, so put the visible text, and the most important words, first.

3. What is self-reference?

Including the element's own id in its aria-labelledby, to keep its own text in the name, such as "Edit Rye bread".

4. Does hidden text count?

Yes. A referenced element that is hidden (with display: none, hidden, or aria-hidden) is still used to build the name.

5. What happens if an id in the list does not exist?

It is silently ignored. You get no error, so the name may be missing or wrong.

6. Why is the same id on several elements a problem?

Browsers use the first match, so every copy points to the first one. Use unique ids built from something unique, such as an item id.

Key Takeaways​

  • aria-labelledby points to text that already exists, so the name stays in one place and translates with the page.
  • It comes first in the naming order, and replaces the element's own content.
  • Use self-reference to keep the visible text, and put it first.
  • Hidden text still counts. Do not put private data in it.
  • Ids must exist and be unique. A typo or a duplicate fails silently.
  • Choose: label for form fields, aria-labelledby for text elsewhere, aria-label when there is no text.
  • Check in the Accessibility panel, and with voice control and a screen reader.

Next, learn how to add extra help to a control with aria-describedby.