In HTML, what is the difference between aria-label, aria-labelledby, and aria-describedby, and when do you reach for each?
answer
- two slots, not three attributes
- name identifies, description supplements
- one takes a string, two take IDs
- labelledby reuses on-screen text
- describedby never substitutes for a name
basics
~20 saria-label supplies an element's accessible name as a literal string, aria-labelledby builds that name from text already on the page by referencing element IDs, and aria-describedby attaches supplementary text announced after the name. Labels identify; descriptions supplement.
solid answer
~50 sAll three feed the accessibility tree, but into two different slots. `aria-label` and `aria-labelledby` both set the **accessible name** — the short string a screen reader announces with the role, and the phrase voice-control users speak. `aria-label` takes a literal string, so you use it when there is no visible text to point at, such as an icon-only button. `aria-labelledby` takes a space-separated list of element IDs and concatenates their text, so you use it when the label is already on screen — a section named by its heading, a dialog named by its title — keeping one source of truth. `aria-describedby` also takes ID references but fills the **accessible description**, announced after the name and typically after a pause: format hints, help text, error text. It never substitutes for a name. And before any of them, prefer real markup: a `<label for>` or actual button text needs no ARIA at all.
code
html · 11 lines<!-- name from a literal string -->
<button type="button" aria-label="Close dialog">✕</button>
<!-- name from visible text elsewhere on the page -->
<h2 id="shipping-heading">Shipping address</h2>
<section aria-labelledby="shipping-heading">…</section>
<!-- name from the label, description from the hint -->
<label for="card">Card number</label>
<input id="card" name="card" inputmode="numeric" aria-describedby="card-hint">
<p id="card-hint">16 digits, no spaces.</p>go deeper
Be able to say which attribute takes plain text and which take element IDs, and that describedby adds extra information rather than naming the control. Naming the icon-only-button case as the classic use of aria-label is enough here.
Explain the name-versus-description split in the accessibility tree, that labelledby concatenates referenced text in the listed order, and that aria-label replaces visible text rather than adding to it. Say when you would pick each.
Show judgment about maintenance: invisible strings drift, translation pipelines miss attribute values, and referenced text stays in sync with the UI. Be ready to argue for native labelling over ARIA in a code review and justify it concretely.
Own the convention across a codebase — where teams may use aria-label at all, how labels get localised, and how lint rules and automated audits catch unnamed controls before they ship rather than after an accessibility audit finds them.
## Name and description are two different slots Every element exposed to assistive technology carries a **role** (what kind of thing it is), an **accessible name** (what it is called) and an **accessible description** (extra information about it). A screen reader typically announces name and role together — "Search, button" — and then, after a short pause or on request, the description. Voice-control software matches a spoken command against the *name*, not the description. Automated test tools and browser accessibility inspectors show both fields. That two-slot model is the whole answer. `aria-label` and `aria-labelledby` write into the name slot; `aria-describedby` writes into the description slot. Confusing the two is the most common mistake: a control whose only text lives in `aria-describedby` has no name at all and is announced as a bare "button". ## aria-label — a literal string ```html <button type="button" aria-label="Close dialog"> <svg aria-hidden="true" focusable="false"><!-- ✕ glyph --></svg> </button> ``` The attribute value *is* the name. Nothing on screen changes; only assistive technology sees it. Reach for it when there is genuinely no visible text to reference — icon-only controls, or a landmark that needs disambiguating (`<nav aria-label="Breadcrumb">`). Its weaknesses follow from being invisible. Nobody reviewing the UI sees it, so it rots when the icon's meaning changes. It is an attribute value, so localisation and translation workflows that only walk visible text can miss it. And it is ignored where the element has no meaningful role: ARIA marks `aria-label` prohibited on roles such as `generic`, which is what a plain `<div>` or `<span>` maps to, so labelling a wrapper `<div>` usually does nothing. ## aria-labelledby — point at text that already exists ```html <h2 id="billing-heading">Billing address</h2> <section aria-labelledby="billing-heading">…</section> ``` The value is a space-separated list of `id` values in the same document. The browser gathers the text content of each referenced element and joins the pieces in the order the IDs are listed. Because the name is derived from what is on screen, it stays in sync with the visible UI and gets translated along with the page. This is the right tool whenever the label is visible: a modal named by its title, a group of radio buttons named by a heading, a table of "Edit" buttons where each one is named by both its own text and the row's item name. ## aria-describedby — supplementary information ```html <label for="pw">Password</label> <input id="pw" type="password" aria-describedby="pw-hint"> <p id="pw-hint">At least 12 characters, including a number.</p> ``` Same ID-reference mechanism, different slot. The input's name is "Password" (from the `<label>`); the hint becomes the description. Screen readers read the description after the name and role, and some verbosity settings shorten or skip descriptions entirely — which is exactly why anything essential to identifying the control belongs in the name instead. Typical uses: input format hints, password rules, a note that a link opens a file of a given size, and the error text bound to an invalid field. ## Precedence, in one line When more than one is present, `aria-labelledby` wins over `aria-label`, and either overrides the name the element would otherwise get from its own content or its associated `<label>`. That is a sharp edge: adding `aria-label` to a button that already reads "Save changes" *replaces* the visible text for screen-reader and voice users rather than adding to it. ## Native markup comes first None of these three is the default answer. A form control paired with `<label for="…">` already has a name, and the label gives sighted mouse users a bigger click target that ARIA cannot provide. A `<button>Delete</button>` names itself. An `<img>` names itself through `alt`. Use the ARIA attributes to fill gaps native HTML cannot — not to re-do work the element already does. ## Choosing, in practice - Visible text exists and is correct → let it name the element, or reference it with `aria-labelledby`. - No visible text at all → `aria-label`. - Extra detail that is helpful but not identifying → `aria-describedby`. - Only a tooltip available → `title` is the last resort: it appears on hover but not reliably on keyboard focus, is unavailable on touch, and is easy for users to never see.
- If an element has both aria-label and aria-labelledby, which one does the browser use?`aria-labelledby` wins. The name computation checks it first, and only falls through to `aria-label` when the reference list resolves to nothing usable. So a stale `aria-label` sitting next to a working `aria-labelledby` is dead weight in the markup, not a fallback anyone will hear — and it is worth deleting so the next reader is not misled about which string is live.
- Why does adding aria-label to a plain <div> often have no effect?A `<div>` with no role maps to the `generic` role, and ARIA lists `aria-label` and `aria-labelledby` as prohibited on that role, so browsers do not expose a name for it. Give the element a role that supports naming — a landmark such as `<nav>` or `<section>`, a `role="group"`, a real interactive element — or move the label onto the element that actually needs it.
- When is title an acceptable way to label a control?Effectively as a last resort, when no better mechanism is available — for example an `<input>` in a compact toolbar with no room for a visible label. It shows as a hover tooltip only: keyboard users generally never see it, touch users cannot trigger it, and its timing is browser-controlled. Prefer a visible `<label>`, or `aria-label` if the space truly cannot hold one.
saying these in an interview costs you the question
- Puts the control's label in aria-describedby, leaving it unnamed
- Thinks aria-label appends to visible text instead of replacing it
- Believes aria-labelledby accepts a CSS selector or class name
- Adds aria-label to wrapper divs and expects announcements
- Reaches for ARIA before trying <label for> or real button text