skip to content

aria-label vs labelledby vs describedby

Three attributes that look interchangeable and are not: one supplies a string, one points at existing text, one attaches supplementary description. Icon-only buttons and error hints are the concrete cases interviewers reach for.

part ofHTMLoverview, primer and where to startread it →
on this pageshow

questions

5

A toolbar button in your HTML contains only an <svg> icon and no text. What are your options for giving it an accessible name, and which would you choose?

level: juniorimportance: must knowfreq 70%

answer

  1. an svg contributes no text
  2. role is fine, name is missing
  3. string, hidden text, or ID reference
  4. hide the glyph from the tree
  5. tooltips are mouse-only

basics

~20 s

Give the button an accessible name with aria-label="Delete", with visually hidden real text inside it, or with aria-labelledby pointing at existing on-screen text. Hide the decorative icon with aria-hidden="true" so it adds nothing. A title tooltip alone is not enough.

solid answer

~50 s

An icon-only `<button>` has no text content, so without help it is announced as just "button". Three workable options: put a literal string in `aria-label="Delete"`; keep real text inside the button and hide it visually with a utility class so it still lands in the accessibility tree; or point `aria-labelledby` at text that already exists on screen. In all three, mark the decorative graphic `aria-hidden="true"` so its internals contribute nothing to the name. I default to `aria-label` for standalone icon controls because it is one attribute and reads clearly, and reach for visually hidden text when the team wants the label to travel through the same translation pipeline as body copy. What I do not do is rely on `title` alone: it is a hover tooltip, so keyboard and touch users never see it, and it is only a weak fallback for the name.

code

html · 14 lines
html
<!-- literal string -->
<button type="button" aria-label="Delete message">
  <svg aria-hidden="true" focusable="false" width="16" height="16" viewBox="0 0 16 16">
    <path d="M3 4h10v9a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4Z"></path>
  </svg>
</button>

<!-- real text, hidden visually but present in the accessibility tree -->
<button type="button">
  <svg aria-hidden="true" focusable="false" width="16" height="16" viewBox="0 0 16 16">
    <path d="M3 4h10v9a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4Z"></path>
  </svg>
  <span class="visually-hidden">Delete message</span>
</button>

go deeper

for a junior

Recall that an SVG contributes no text, so the button needs a name from aria-label or from hidden real text, and that the icon should be marked aria-hidden="true". Say why a title tooltip is not sufficient.

for a middle

Explain the tradeoff between an invisible attribute string and visually hidden content — maintenance, localisation, review visibility — and know that display: none removes text from the accessibility tree while a clipping utility does not.

for a senior

Talk about the systemic fix: a shared icon-button component that makes the name a required prop, a lint rule for nameless interactive elements, and verification in the accessibility inspector rather than trust in the attribute.

for a principal

Own the policy across teams — where icon-only controls are allowed at all, how their labels get translated, and how naming failures are caught in CI so accessibility is not re-litigated component by component.

## Why the button starts out nameless The accessible name of a `<button>` normally comes from its own text content. An icon-only button has none — the `<svg>` inside it is graphics, and its `<path>` data is not text — so the accessibility tree exposes a control with a role and an empty name. Screen-reader users hear "button" with no idea what it does; voice-control users have nothing to say to activate it; automated audits flag it as a nameless interactive element. ## Option 1 — aria-label ```html <button type="button" aria-label="Delete message"> <svg aria-hidden="true" focusable="false" width="16" height="16">…</svg> </button> ``` One attribute, name solved. `aria-hidden="true"` on the icon removes it and its subtree from the accessibility tree so nothing stray leaks into the name; `focusable="false"` guards against older browsers putting inline SVG in the tab order. The drawback is that the string is invisible to everyone reviewing the UI. It is easy for the icon to change meaning while the label stays behind, and translation workflows that walk visible text can skip attribute values. ## Option 2 — visually hidden real text ```html <button type="button"> <svg aria-hidden="true" focusable="false">…</svg> <span class="visually-hidden">Delete message</span> </button> ``` The text is genuine content: it is in the DOM, it names the button through ordinary content-derived naming, and it flows through the same localisation path as the rest of the page. The catch is that the hiding technique matters. A class that uses `display: none` or `visibility: hidden` removes the text from the accessibility tree too, and you are back to a nameless button. The utility class has to keep the element rendered while clipping it out of view — that is why teams keep a single shared, tested "visually hidden" utility rather than improvising one per component. ## Option 3 — aria-labelledby ```html <h3 id="msg-42-subject">Q3 budget review</h3> <button type="button" aria-labelledby="del-label msg-42-subject"> <span id="del-label" hidden>Delete</span> <svg aria-hidden="true" focusable="false">…</svg> </button> ``` Worth it when the useful part of the name already exists on screen — most often in a list where a dozen identical icon buttons need to be told apart. Listing several IDs concatenates their text in the order given, producing "Delete Q3 budget review". ## What not to do `title="Delete"` alone is the tempting shortcut and the weakest option: the tooltip appears on mouse hover, generally not on keyboard focus, and not at all on touch, so most users never see it. It can serve as a fallback for the name when nothing better exists, but designing around it means designing for mouse users only. A bare `<div>` or `<span>` with a click handler is worse still: no name, no keyboard focus, no Enter/Space activation, no role. Start from `<button>` and the only thing left to solve is the name. Finally, do not describe the picture. The name should say what the control *does* — "Delete message" — not what the glyph looks like ("trash can icon"). And avoid stuffing the word "button" into the label; the role is already announced, so `aria-label="Delete button"` produces "Delete button, button". ## Getting the name right Write the name the way a user would refer to the action out loud, keep it short, and make it unique within its context — three buttons all named "Edit" in one list are technically named and practically useless. Then verify it: open the browser's accessibility inspector and read the computed Name field for the button rather than assuming the attribute took effect.

  • Why put aria-hidden="true" on the icon if you have already set aria-label on the button?
    Defence in depth and cleanliness. If the SVG carries a `<title>` element or stray text, it can surface in the accessibility tree or in the name computation when the label is later removed. Marking the graphic `aria-hidden="true"` says plainly that it is decorative, keeps the tree free of a meaningless image node, and makes the button's single source of naming obvious to the next reader.
  • A teammate hides the button's text with a class that sets display: none. What breaks?
    The name. `display: none` removes the element from rendering *and* from the accessibility tree, so the text contributes nothing and the button is announced as unnamed again. The same applies to `visibility: hidden` and the `hidden` attribute. A visually-hidden utility must keep the element rendered — that is the whole difference between the two techniques.
  • How would you name ten identical icon buttons in a list of messages?
    Make each name unique by including the row's subject. Either build it per row with `aria-label="Delete Q3 budget review"`, or use `aria-labelledby` listing an ID for the action word plus the ID of the row's existing heading, which concatenates to the same thing without duplicating strings. "Delete" ten times over is technically named but leaves the user unable to tell the controls apart.

saying these in an interview costs you the question

  • Relies on title alone and calls the button labelled
  • Names the glyph, not the action: "trash can icon"
  • Uses a div with a click handler instead of <button>
  • Hides the label text with display: none
  • Writes aria-label="Delete button", duplicating the role

context

open as a page

In HTML, what is the difference between aria-label, aria-labelledby, and aria-describedby, and when do you reach for each?

level: middleimportance: must knowfreq 76%

basics

~20 s

aria-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.

open as a page

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?

level: middleimportance: should knowfreq 48%

basics

~20 s

aria-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.

open as a page

A password field in a form has a permanent format hint under it, and on a failed submit an error message appears in the same area. How do you wire both to the input in HTML, and what does aria-describedby not do for you?

level: seniorimportance: should knowfreq 52%

basics

~20 s

Give the hint and the error each an id and list both in the input's aria-describedby, in the order you want them read, alongside aria-invalid="true" on failure. aria-describedby only attaches text to the field; it does not announce anything on its own.

open as a page

An audit flags <button aria-label="Add to cart">Buy now</button>. What problem does the mismatch between the aria-label and the visible text create, and how would you fix it?

level: seniorimportance: should knowfreq 36%

basics

~20 s

The aria-label replaces the visible text in the accessibility tree, so a voice-control user who says "click Buy now" matches nothing and cannot activate the button. Make the accessible name start with, or contain, the visible words.

open as a page