skip to content

A page contains `<button aria-labelledby="t" aria-label="Close" title="Dismiss dialog">Save</button>` and `<span id="t">Cancel</span>`. Which string does a screen reader announce as the button's name, and what is the general precedence order of naming sources?

level: middleimportance: must knowfreq 62%

answer

  1. several candidates, exactly one winner
  2. first match wins, nothing is merged
  3. author overrides beat native markup
  4. labelledby before label before native
  5. title is the bottom of the list

basics

~20 s

The button is announced as "Cancel". The accessible name computation takes the first source that yields text, in order: aria-labelledby, then aria-label, then the element's native source such as text content, alt, label, caption or legend, and finally title.

solid answer

~40 s

It announces "Cancel", because `aria-labelledby` sits at the top of the precedence order and its referenced element contains that text. The computation walks an ordered list and stops at the first source that produces a non-empty string: `aria-labelledby` first, then `aria-label`, then the element's **native** host-language source — subtree text for a button or link, `alt` for an image, the associated `<label>` for a form control, `<caption>` for a table, `<legend>` for a fieldset — and `title` last, as a fallback only. The sources do not concatenate; the winner replaces the others entirely. That is why the visible word "Save" is never announced here, and it is the mechanism behind the classic bug where someone adds an `aria-label` for tidiness and silently overrides perfectly good visible text.

code

html · 12 lines
html
<!-- name: "Save" — subtree text, no author override -->
<button>Save</button>

<!-- name: "Close" — aria-label outranks the subtree text -->
<button aria-label="Close">Save</button>

<!-- name: "Cancel" — aria-labelledby outranks aria-label -->
<span id="t">Cancel</span>
<button aria-labelledby="t" aria-label="Close">Save</button>

<!-- name: "Save" — title loses to the subtree text and becomes the description -->
<button title="Dismiss dialog">Save</button>

go deeper

for a junior

Memorise the order — aria-labelledby, then aria-label, then the native source such as text or alt or label, then title — and be able to read a snippet and say which string wins.

for a middle

Explain that the computation stops at the first non-empty source and never merges sources, and justify why author overrides are placed above native markup in the order.

for a senior

Demonstrate that you diagnose naming surprises by reading the computed name and its source in the accessibility tree, and that you flag overrides that contradict visible text during review of shared components.

for a principal

Own the component API consequences: decide whether design-system primitives forward labelling props at all, since an override that silently outranks children's visible text is a defect the order guarantees will happen at scale.

## Several candidates, one name An element can carry many things that look like labels at once: visible text, an `aria-label`, an `aria-labelledby` reference, a `title`, a `<label>`, a `placeholder`. Assistive technology announces exactly one string. The accessible name computation is the algorithm that decides which, and it is an **ordered first-match-wins** walk, not a merge. Understanding the order is what turns "the screen reader says something weird" into a two-second diagnosis. ## The order From highest precedence to lowest: 1. **`aria-labelledby`** — one or more IDs of other elements. The browser gathers the text of each referenced element, in the order the IDs are listed, and joins it. If it yields any text, the walk stops here. 2. **`aria-label`** — a literal string on the element itself. 3. **The native host-language source** — whatever HTML defines for that element: subtree text content for `<button>`, `<a>` and headings; `alt` for `<img>`; the associated `<label>` for a form control; `<caption>` for `<table>`; `<legend>` for `<fieldset>`; `<figcaption>` for `<figure>`. 4. **`title`** — the tooltip attribute, used only when nothing above produced a name. For a text input there is also `placeholder` in the fallback tier alongside `title`, and neither is a substitute for a real label — both are last-ditch sources, and the placeholder vanishes as soon as the user types. ## Walking the example ```html <span id="t">Cancel</span> <button aria-labelledby="t" aria-label="Close" title="Dismiss dialog">Save</button> ``` Step 1 finds `aria-labelledby="t"`, resolves the ID to the span, and reads "Cancel". Non-empty, so the walk stops. `aria-label`, the subtree text "Save" and the `title` are all ignored **for the name**. The computed name is "Cancel", and the screen reader announces "Cancel, button" while the user is looking at a button that says Save. That markup is a bug, of course — nobody writes it on purpose. It gets assembled over time: a design-system button forwards an `aria-label` prop, a consumer adds `aria-labelledby` for a tooltip, and the visible text stops being the name. ## Why author overrides sit on top The order is not arbitrary. ARIA attributes exist precisely to say "the markup does not express this correctly, here is the truth", so an author's explicit statement must beat whatever the element would have said natively. `aria-labelledby` beats `aria-label` because it points at real, rendered content that stays in sync with what the user sees and with the page's translation, whereas `aria-label` is a detached literal string that translators and future maintainers routinely miss. The corollary is a discipline: an override that outranks visible text should reproduce that text, not contradict it. If a button reads "Save", its accessible name should contain "Save". ## title is a fallback, not a label `title` sits at the bottom for good reasons. It shows only on mouse hover, is unreachable by keyboard and touch, is inconsistently exposed by screen readers, and is not translated in the same pipelines as body copy. It is a genuine name source, so an unlabelled control with a `title` is better than a nameless one — but it is a symptom of missing markup, not a design choice. A `title` on an element that already has a name is not wasted: it is demoted to the **accessible description** instead, a separate property announced after the name. ## Sources do not concatenate A persistent misconception is that `aria-label` appends to visible text, giving "Save Close". It never does. The winning source replaces the rest wholesale. The one place where several strings *are* joined is inside a single source: `aria-labelledby="a b c"` concatenates the text of those three elements in the listed order. ```html <span id="verb">Delete</span> <span id="obj">invoice 42</span> <button aria-labelledby="verb obj">🗑</button> <!-- name: "Delete invoice 42" --> ``` ## The debugging move When the announced name surprises you, do not read the markup harder — open the Accessibility pane in Chrome DevTools or the Firefox Accessibility Inspector. Both show the computed name **and** the source that won, which collapses the whole question to one glance. In review, treat any `aria-label` on an element that already has visible text as something to justify, because by this order it is guaranteed to be overriding it.

  • If sources never concatenate, why does `aria-labelledby="a b"` produce two words?
    Because the joining happens *inside* one source, not across sources. `aria-labelledby` accepts a space-separated ID list and gathers the text of each referenced element in the order listed, so `aria-labelledby="verb obj"` yields "Delete invoice 42". Once that source returns a non-empty string the walk stops — `aria-label`, subtree text and `title` still contribute nothing.
  • What happens to a `title` attribute on an element that already has a name from another source?
    It is not discarded — it becomes the element's accessible **description** instead. Name and description are separate properties: the name identifies the control and is announced first, the description is supplemental and comes after a pause. So `<button title="Dismiss dialog">Save</button>` has the name "Save" and the description "Dismiss dialog".
  • Why is putting an `aria-label` on a button that already shows visible text usually a defect?
    Because `aria-label` outranks subtree text, so it silently replaces the visible label. Screen-reader users then hear a different word from the one sighted users read, and voice-control users cannot activate the control by saying what they see. If an override is genuinely needed, its text must contain the visible label rather than contradict it.

saying these in an interview costs you the question

  • Says visible text always wins over aria-label
  • Thinks aria-label and text content are concatenated
  • Treats title as a reliable labelling mechanism
  • Believes the last attribute in source order wins
  • Thinks aria-describedby competes for the name

context