In HTML, how does aria-labelledby resolve when its value lists several IDs, and what happens if one of those IDs does not exist on the page?
answer
- IDs, not selectors
- attribute order, not DOM order
- bad reference fails without warning
- duplicate ids collapse repeated names
- an element may reference itself
basics
~20 saria-labelledby takes a space-separated list of element IDs, gathers each referenced element's text, and joins the pieces in the order the IDs are listed, not DOM order. IDs that match nothing are skipped silently and contribute no text.
solid answer
~50 sThe value is an ID-reference list — space-separated `id` values from the same document, not a CSS selector. The browser looks each one up, takes its text, and concatenates the results **in the order the IDs appear in the attribute**, which is not necessarily the order those elements appear in the DOM. An ID that matches nothing is simply skipped: there is no error, no console warning, just a shorter name, which is why a typo or a renamed ID produces a silently wrong label. If none of the references resolve to text, the element falls through to whatever it would otherwise be named by. The list may include the element's own ID, which is how you compose a name like "Delete Q3 budget review" from a button's own word plus the row heading it belongs to. And only one aria-labelledby attribute exists per element — you extend the name by adding IDs to the list, not by repeating the attribute.
code
html · 9 lines<h3 id="row-7-title">Q3 budget review</h3>
<!-- self-reference plus the row heading -->
<!-- accessible name: "Delete Q3 budget review" -->
<button id="del-7" type="button" aria-labelledby="del-7 row-7-title">Delete</button>
<!-- attribute order decides the reading order -->
<!-- accessible name: "Q3 budget review Delete" -->
<button id="del-8" type="button" aria-labelledby="row-7-title del-8">Delete</button>go deeper
Know that the value is a list of element IDs from the same page, not text and not a selector, and that referencing several IDs joins their text into one name.
Explain that concatenation follows attribute order rather than DOM order, that unmatched IDs are skipped without any error, and that an element may include its own ID to fold its content into the name.
Show how you catch the silent-failure modes in a real app: per-row generated IDs, duplicate-id validation, and reading the computed Name in the accessibility inspector rather than trusting the markup.
Own the convention for generating stable unique IDs in component libraries and server-rendered lists, and decide when a team should prefer native label association over ID-reference labelling entirely.
## The value is an ID-reference list ```html <span id="verb">Delete</span> <h3 id="row-7-title">Q3 budget review</h3> <button type="button" aria-labelledby="verb row-7-title">…</button> ``` `aria-labelledby` does not take text, a selector, a class, or a query. It takes IDs, whitespace-separated, resolved against the same document. That is the first thing to get right, because `aria-labelledby=".row-title"` and `aria-labelledby="#verb"` both look plausible and both resolve to nothing. ## Concatenation order is attribute order The browser walks the ID list left to right, computes the text of each referenced element, and joins the results with spaces. Attribute order wins over document order: if `row-7-title` appears earlier in the DOM but later in the list, the name still reads "Delete Q3 budget review". Reordering the attribute value is the supported way to change how the name reads. The text of each referenced element is computed recursively — a referenced `<h3>` containing a `<span>` yields the whole heading's text — but references are not chased transitively. If a referenced element itself carries `aria-labelledby`, that inner reference is not followed a second level down; the recursion is deliberately bounded so a chain of labels cannot loop. ## Missing IDs fail silently A reference that matches no element in the document contributes nothing and produces no diagnostic. This is the single most common way a label goes wrong in production: - someone renames an element's `id` and misses the reference, - a component renders the referencing element but conditionally omits the referenced one, - IDs are duplicated across a repeated row, so every button in the list references the *first* matching element and they all end up with the same name. That last one is worth stressing: `id` must be unique in the document, and duplicate IDs in a list rendering silently collapse ten distinct button names into one repeated name. When markup is generated per row, the IDs have to be generated per row too. If every reference in the list fails, the attribute contributes an empty name and the computation continues on to the next source — `aria-label`, a native `<label>`, element content — so the visible symptom is often not "no name" but "the old name reappeared", which sends people hunting in the wrong place. ## Self-reference is legal and useful ```html <button id="del-7" type="button" aria-labelledby="del-7 row-7-title">Delete</button> ``` Including the element's own ID pulls in its own content as one piece of the name. This is the standard pattern for lists of repeated actions: the button keeps its short visible word, and the name announced is the unambiguous "Delete Q3 budget review". It also avoids duplicating the row title as an invisible string that can drift out of sync with the visible one. ## Hidden references and other sharp edges Two more things bite in practice. First, `aria-labelledby` overrides everything else the element would be named by, including a properly associated `<label>` — so a form control with both is named by the reference, and the visible label becomes purely decorative from the accessibility tree's point of view. Second, the attribute names the element; it does not create the two-way association a native `<label for>` does. Clicking the referenced text does not focus the input, and the referenced element does not gain any behaviour. If you want the click target as well, use `<label for>` and skip the ARIA. ## How to verify Do not eyeball the attribute — read the computed result. Every major browser's accessibility inspector shows the element's Name field along with where each piece came from, which immediately reveals a reference that resolved to nothing. Pairing that check with an automated audit that flags nameless controls catches the silent-failure class of bug before it reaches users.
- Your button's aria-labelledby references an ID that was renamed. What does the user experience?Nothing announces the failure. That reference contributes no text, so the name is either shortened or — if it was the only reference — falls through to the next available source, such as the button's own content. The result is a control that sounds subtly wrong or generically named, with no console warning and no visual change, which is why the accessibility inspector's Name field is the thing to check.
- A list renders ten rows and every row reuses id="row-title". What goes wrong?`id` must be unique per document. With duplicates, every reference resolves to the first matching element, so all ten buttons compute the same name — "Delete Q3 budget review" ten times — and the user cannot distinguish them. Generate IDs per row from the record's key, and let an HTML validator or lint rule catch duplicates in CI.
- Does aria-labelledby give you the same click-to-focus behaviour as <label for>?No. `aria-labelledby` only sets the accessible name in the accessibility tree; it creates no interactive association. Clicking the referenced text does not move focus to the control and does not toggle a checkbox. When the control is a form field and the text is visible, `<label for>` is the better choice because it delivers both the name and the larger pointer target.
saying these in an interview costs you the question
- Passes a CSS selector or class name instead of IDs
- Assumes DOM order decides the concatenation order
- Expects a console error when a reference is missing
- Repeats the attribute instead of adding IDs to the list
- Reuses the same id across every row of a list