Naming and describing
Every control needs a name. Browsers compute it from a stack of fallbacks — and one of them always wins.
When focus lands on an interactive element, a screen reader announces three things in order: the accessible name, the role, and any relevant state — for example, "Subscribe, button" or "Send to all, checkbox, checked". The name is the part you control most directly, and the part most screens get wrong.
This lesson covers the three ARIA attributes that name or describe things — aria-label, aria-labelledby, aria-describedby — plus the rules a browser follows to pick which name actually wins.
Name versus description
A name is the short label. "Search", "Subscribe", "Email address". It's the first thing announced and what voice users say to activate the control.
A description is the optional longer explanation. "Search by author or title", "We email once a week. Unsubscribe any time." It's announced after the name, and only if the user pauses on the element.
These map to two attributes. aria-label and aria-labelledby set the name. aria-describedby sets the description. Mixing those up is one of the most common bugs in production code.
Quick check: read your control out loud as name, role, state. If the sentence is awkwardly long, you've probably stuffed the description into the name.
aria-label
aria-label puts a name on an element using a string you write directly:
<button aria-label="Close dialog">×</button> <a href="/" aria-label="Home"> <img src="/logo.svg" alt=""> </a>
The visible content of the element is overridden — the screen reader reads "Close dialog, button" instead of "X". Use this when there's no visible text, or when the visible text isn't a useful name on its own (a magnifying-glass icon, an "X").
The trade-off: the name now lives only in code. Translators won't see it the way they see visible text, voice users have to guess what you wrote, and there's nothing on screen tying the name to the control. Reach for it when there's no other option.
aria-labelledby
aria-labelledby points to one or more other elements on the page and uses their text content as the name. You give it a space-separated list of IDs:
<h2 id="dialog-title">Delete account?</h2> <p id="dialog-warning">This cannot be undone.</p> <div role="dialog" aria-labelledby="dialog-title"> ... </div> <!-- Combine multiple labels into one name --> <button aria-labelledby="action-verb item-name">Delete</button> <span id="action-verb">Delete</span> <span id="item-name">"Q4-report.pdf"</span> <!-- Announced as: "Delete Q4-report.pdf, button" -->
This is the preferred name attribute when the label is already on screen. The screen reader uses the referenced text verbatim; if you change the visible heading later, the name updates automatically. If multiple IDs are listed, the names concatenate in the order you wrote them.
A subtle gotcha: aria-labelledby references are computed once and look at the element's text content, including any text from descendants. They don't run JavaScript and they don't fall back if the ID doesn't exist — a typo means no name at all.
aria-describedby
aria-describedby is the same shape as aria-labelledby — a list of element IDs — but it sets the description, not the name. The classic use is associating help text or an error message with a form field:
<label for="pw">Password</label> <input id="pw" type="password" aria-describedby="pw-help pw-error"> <p id="pw-help">At least 12 characters, including a number.</p> <p id="pw-error">Password is too short.</p>
The screen reader announces: "Password, edit, At least 12 characters... Password is too short." Name first, then description. Sighted users see the help text on the page; screen-reader users hear it tied to the input automatically.
Don't shove sentence-long text into aria-label. Long names get cut off, sound clunky, and voice users can't repeat them. Long context belongs in aria-describedby.
How the name is calculated
Browsers follow a specific order — the accessible name computation — to figure out a control's name. Roughly, from highest precedence to lowest:
aria-labelledbywins if it points to one or more existing elements with content.aria-labelwins next.- The element's native labeling mechanism —
<label for="...">for form controls, the<caption>for a table, the visible content for<button>, thealtfor an<img>. - Tooltips (
titleattribute) come last, as a fallback.
Whichever step finds a name first, the browser stops looking. This means a button with both visible text and an aria-label will announce the aria-label — possibly losing the visible text entirely.
A practical consequence: if <button aria-label="Save">Submit</button> is announced as "Save, button" because the label wins, the visible word "Submit" is silently dropped. A voice user saying "click Submit" hits nothing. Match what you write.
Try it yourself
<button aria-label="Help">Get more info</button>?<button aria-labelledby="verb noun">Delete</button> do, given <span id="verb">Remove</span> and <span id="noun">item</span>?<button><svg>...</svg></button>. Without changes, what's the announced name?