Every control on your page has an accessible name, and a browser works it out by running down a fixed list and taking the first thing that produces text. aria-labelledby beats aria-label, which beats the element's own label, which beats the text inside the element, which beats the title attribute. First match wins, and the rest are discarded without warning.
That one algorithm decides the outcome of five separate WCAG rules. Naming non-text content, naming a link from its own text, naming a link from its surrounding context, Name, Role, Value and Label in Name all resolve to it, which is why the same handful of bugs keeps appearing under different rule numbers.
It is why an aria-label on a button silently deletes the words printed on that button, which breaks voice control while looking like a fix. It is why a field with nothing but a placeholder passes an automated check for Name, Role, Value and fails Labels or Instructions instead. And it is why aria-label on a bare <div> does nothing at all.
The Order a Browser Checks, First Match Wins
The full algorithm has a step for recursion and a step for controls embedded inside other controls, and neither changes anything you will author by hand. What follows is the part authors control, in order, and it runs top to bottom until something produces text.
- Is naming allowed on this role at all? If the role forbids naming, the name is empty and nothing below this line runs. That check happens first, before any attribute is read.
aria-labelledby. The referenced elements are read in the order the attribute lists them and joined with spaces. This wins over everything else, including text the reader can see.aria-label. The literal string you wrote. Nothing visible on the page is consulted once this produces text.- The host language's own labeling. For an image that is the
altattribute, for a form field the associated<label>, for an SVG the<title>element. HTML decides this part, not ARIA. - The element's own content. The text inside the button, the link or the heading. This is where a plain
<button>Save</button>gets its name. - The
titleattribute. Last, and used only when nothing above produced anything, including the element's own content.
Reading that list downwards explains the fixes. Reading it upwards explains the bugs, because every attribute near the top is capable of erasing something further down that a person can actually see.
The one line that decides half of all naming findings
A name may be invisible. A label is visible to everyone. WCAG defines them as two different things, and they are frequently but not always the same string. That is the whole reason Label in Name exists as a separate rule, and it is why a control can have a perfect accessible name and still be unusable by somebody speaking to their computer.
Why aria-label Quietly Deletes Your Button Text
Put aria-label="Submit" on a button reading Buy now and the button is now called Submit. The visible words are still there, still printed, and no longer part of the name, because aria-label sits above element content on the list and the browser stopped looking.
For a screen reader user that is a mismatch between what they hear and what a sighted colleague sees. For somebody driving the browser by voice it is worse than a mismatch, because it is a control they cannot operate. They say click Buy now, the software looks for a control named Buy now, and there is not one. Label in Name exists for exactly this, sits at Level A, and asks that where a component has a visible text label, the accessible name contains that text.
Two details change how that rule is tested. It is case-insensitive and ignores most punctuation, because capitalization and punctuation are generally ignored when somebody speaks a label. And the instruction is to bias towards treating only adjacent text as the label, because a generous reading of what counts as a label undermines the rule. A heading three sections up is not the button's label.
The remedy is the sentence most teams never reach. If a control needs extra context for a screen reader user, that context belongs in aria-describedby, which feeds the accessible description rather than the name. Screen readers announce a description right after the name, so the reader gets the context, the visible label stays intact, and voice control keeps working. Descriptions are a separate computation with their own order, and nothing in them touches the name. How accessible descriptions are computed is the other half of this page, and it covers that order and the ways it goes wrong.
The Roles That Cannot Be Named At All
Eleven ARIA roles forbid naming outright. They are caption, code, deletion, emphasis, generic, insertion, paragraph, presentation, strong, subscript and superscript. On any of them, ARIA states that authors must not use aria-label or aria-labelledby, and a browser is entitled to ignore the attribute entirely.
One of those eleven does far more damage than the other ten combined. A bare <div> or <span> maps to role generic, which is on the list. So aria-label on a roleless div is not a harmless extra, it is a naming attribute on a role that prohibits naming, and it produces nothing. That is one of the most common wasted fixes in the wild, and it is usually applied by somebody who has correctly identified that a control has no name.
The order of operations is what teams miss. Add the role first, then the name. Giving a custom widget a real ARIA role is what makes the label attribute mean anything, and until it is there the label is dead markup. Better still, use the native element, which brings its role and its states along at no cost.
The Roles That Must Be Named
Thirty-four roles run the other way and require a name. A control in one of these with no accessible name is incomplete by its own role definition, before WCAG is consulted at all, which makes this list usable directly as an audit sweep.
- Controls somebody operates. button, checkbox, combobox, link, menuitem, menuitemcheckbox, menuitemradio, option, radio, searchbox, slider, spinbutton, switch, textbox.
- Containers somebody lands inside. alertdialog, application, columnheader, dialog, grid, listbox, radiogroup, region, rowheader, table, tabpanel, tree, treegrid, treeitem.
- The remaining six. heading, img, marquee, meter, progressbar, tooltip.
The named failures for an unnamed control are worth citing in a report, because a named failure is checkable and an opinion is not. A div built into a button with no role, a form control with no programmatically determined name, a visible label with no accessible name behind it, the separate parts of a split field left unnamed, and an image that is the only content in a link.
What Counts as Hidden When a Name Is Computed
Hidden has a narrow and slightly surprising meaning here, and it is the reason the visually-hidden pattern works at all. display:none, visibility:hidden, visibility:collapse, content-visibility:hidden and aria-hidden="true" all count as hidden, and text hidden that way is skipped.
Text at opacity:0, text under an opacity filter, text positioned off screen and text covered by another object are not hidden for this purpose. They stay in the accessibility tree and they can name on-screen objects. So a visually-hidden utility class built on clipping or off-screen positioning supplies a real accessible name, and the same class rebuilt on display:none silences the control completely. That is a one-line CSS change that produces an accessibility regression no visual review will catch.
There is a deliberate exception in the other direction. Text that is genuinely hidden can still be pulled into a name when aria-labelledby points straight at it, because an explicit reference overrides the hidden test.
Native Elements Follow Their Own Order, and Placeholder Is On It
For native HTML controls the middle of the list is filled in by HTML's own mapping rules rather than by ARIA, and for a text input or a textarea that order runs aria-labelledby and aria-label, then the associated <label>, then title, then placeholder, then aria-placeholder.
Read that last part again, because it settles an argument that comes up in nearly every form audit. A placeholder supplies an accessible name. A field with a placeholder and no label therefore has a name, passes the automated rule for Name, Role, Value, and appears clean in a scan. It fails Labels or Instructions instead, because a placeholder is not presented persistently to everyone and disappears the moment somebody starts typing. Same field, same problem, different rule number, and filing it under the wrong one is how a finding gets argued away by a developer who ran a scanner.
The fix is the ordinary one. Associate a real label with every field and let the placeholder go back to being an example of the expected format, or nothing at all. Our accessible forms guide covers the rest of the pattern.
The Name Comes Out as One Flat String
Whatever went in, what comes out is a single unstructured string. Accessibility APIs support nothing else, so headings, emphasis, line breaks and language changes inside a name are all discarded. A button containing a small layout of several elements produces one run-on announcement, and a language change marked inside a name is lost, which is why a language switcher labeled in its own language does not always sound the way its author intended.
Three smaller consequences of the same flattening are worth knowing before you debug one. CSS generated content counts, so text injected through ::before or ::after is prepended and appended to the name, and an icon font can end up putting a glyph inside it. Each node is consulted only once, which stops the algorithm looping. And aria-labelledby is followed one step only, so pointing at an element that is itself labeled by something else leaves you with no name rather than a chain.
What Automated Testing Sees Here, and What It Misses
This is the one corner of the standard where scanners are genuinely useful, and their limit is exact rather than vague. W3C files fifteen published test rules under Name, Role, Value, more than for any other criterion, and ten of them name it as the thing they test. Seven of those ten ask the same question in seven places, which is whether a control has a name at all, and between them they cover buttons, links, form fields, menu items, image buttons, summary elements and iframes.
Every one of those seven tests whether a name is non-empty. Not whether it is right, not whether it matches the visible label, not whether it describes what the control does. A button named button passes all seven. A link named click here passes all seven. A submit control labeled Submit on a page where the visible text says Buy now passes the naming rules and fails Label in Name, and nothing in that set is looking for the mismatch. The remaining three rules test something else entirely, being focusable content hidden from the accessibility tree, focusable content inside an element whose children are presentational, and two iframes carrying the same name for different purposes.
So a clean automated report on this criterion means every control has a name. It does not mean any of those names is usable, and the distance between those two statements is most of what a manual pass is for.
How to Check a Name Yourself, in Two Minutes
- Open the accessibility tree in your browser's developer tools and select the control. The computed name is shown as a property, along with the source it came from, which is faster than reasoning about the order.
- Read the name against the visible text. If there is visible text, the name has to contain it. Case and punctuation do not matter. Extra words at the end are fine, and the stated best practice is to have the visible label at the start of the name.
- Say the visible label out loud as a command. That is what a voice control user will do. If the name does not contain those words, the control cannot be reached that way.
- Check the icon-only controls last and separately. They are where names are missing most often, and labeling them with
aria-labelis legitimate precisely because there is no visible text to overwrite.
One honest limit
Tools catch some wrong names, not none, and the boundary is worth knowing. The engine behind our scan compares a control's visible text with its accessible name and flags the mismatch, which is a wrongness check rather than an absence check. Our ARIA validator checks roles, states and the attribute combinations that produce silent failures, and our link text checker catches names that say nothing on their own. What none of them do is read your interface and judge whether Open cart was the right two words. That part is a person, every time.