An accessible name is the short string a screen reader announces when focus reaches a control. For ARIA widgets — role="dialog", role="tooltip", role="progressbar", role="meter", role="treeitem", and the menu-command roles (menuitem, menuitemcheckbox, menuitemradio) — the name mustexist or the widget is announced as nothing more than its role ("dialog," "tooltip," "progress bar") with no clue what it is for.
aria-dialog-name, aria-tooltip-name, aria-progressbar-name, aria-meter-name, aria-treeitem-name, and aria-command-name all fail when the widget has no accessible name.aria-labelledby pointing at visible text, or aria-label as a last resort.<dialog>, <progress>, and <meter> elements still need a name, but they accept the <label> association just like form fields.Think of an accessible name the way you think of the To: line on an envelope: the role tells the postal worker the kind of mail (dialog, progress bar, tooltip), but without a name on the envelope, nobody knows where it is supposed to go.
ARIA widgets are usually the most interactive parts of a page — they are dialogs that interrupt the flow, tooltips that add context, progress indicators that report long-running work. When they have no name, screen-reader users hit a wall the moment focus enters the widget.
treeitem being announced by name. Unnamed items collapse the entire structure into a featureless list.menuitem with no text content and no aria-labelis announced as just "menu item," making keyboard navigation through a menu impossible.The W3C's Accessible Name and Description Computation algorithm walks through several sources of a name in order. For ARIA widgets, three sources matter in practice:
If the widget has visible text inside it, that text becomes the name automatically. This is the strongest option because the visible label and the announced label cannot drift apart.
aria-labelledby pointing at visible text elsewhereWhen the visible label sits outside the widget (a heading above a dialog, a status line near a progress bar), reference its id with aria-labelledby. The label remains visible to sighted users.
aria-label as a fallbackUse only when there is no visible text to point at — for example, an icon-only tooltip trigger or a treeitem that contains nested controls. The string is invisible to sighted users, so visible and announced labels cannot be cross-checked.
The First Rule of ARIA still applies: if a native HTML element exists (<dialog>, <progress>, <meter>), prefer it. The naming rules below apply equally — native elements with no accessible name fail the same audits.
Every role="dialog" or role="alertdialog" (and the native <dialog> element) must announce a name when it opens. The most common pattern is to point aria-labelledbyat the dialog's visible heading.
<!-- BAD: opens as just "dialog" -->
<div role="dialog" aria-modal="true">
<h2>Delete account</h2>
<p>This action cannot be undone.</p>
</div>
<!-- GOOD: heading provides the name -->
<div role="dialog" aria-modal="true" aria-labelledby="del-title">
<h2 id="del-title">Delete account</h2>
<p>This action cannot be undone.</p>
</div>
<!-- GOOD: native <dialog> with the same association -->
<dialog aria-labelledby="del-title">
<h2 id="del-title">Delete account</h2>
</dialog>role="tooltip" elements need a name so the screen reader can announce the tooltip text when the trigger is focused. The simplest fix is visible text content inside the tooltip element, surfaced via aria-describedby on the trigger.
<!-- BAD: empty tooltip element -->
<button aria-describedby="tip-1">Save</button>
<div id="tip-1" role="tooltip"></div>
<!-- GOOD: tooltip has visible text content -->
<button aria-describedby="tip-1">Save</button>
<div id="tip-1" role="tooltip">Saves and continues to step 2</div>role="progressbar" (and the native <progress> element) needs a name explaining what is progressing. Pair it with aria-valuenow, aria-valuemin, and aria-valuemax so the value is also exposed.
<!-- BAD: announced as "65%, progress bar" — but progress on what? -->
<div role="progressbar" aria-valuenow="65" aria-valuemin="0" aria-valuemax="100"></div>
<!-- GOOD: visible label with aria-labelledby -->
<span id="upload-label">Uploading attachment</span>
<div role="progressbar"
aria-labelledby="upload-label"
aria-valuenow="65" aria-valuemin="0" aria-valuemax="100"></div>
<!-- GOOD: native <progress> with <label> -->
<label for="up">Uploading attachment</label>
<progress id="up" value="65" max="100"></progress>A meter is different from a progress bar — it represents a fixed measurement (disk usage, password strength, fuel level), not a process. role="meter" still requires a name explaining what is being measured.
<!-- BAD: just "0.6, meter" -->
<div role="meter" aria-valuenow="0.6" aria-valuemin="0" aria-valuemax="1"></div>
<!-- GOOD: aria-label on the role -->
<div role="meter"
aria-label="Disk usage"
aria-valuenow="0.6" aria-valuemin="0" aria-valuemax="1"
aria-valuetext="60%"></div>
<!-- GOOD: native <meter> with <label> -->
<label for="disk">Disk usage</label>
<meter id="disk" value="0.6" min="0" max="1">60%</meter>Each role="treeitem" must have an accessible name so keyboard users can identify each node in a hierarchical tree. Visible text inside the item is the cleanest source. Wrapper elements (icons, action buttons) sometimes swallow the text — point aria-labelledby at the visible label inside the row to fix it.
<!-- BAD: icon-only treeitem with no name -->
<li role="treeitem">
<svg aria-hidden="true">...</svg>
</li>
<!-- GOOD: visible text inside the treeitem -->
<li role="treeitem">Q4 reports</li>
<!-- GOOD: aria-labelledby when the row has more than one piece of text -->
<li role="treeitem" aria-labelledby="row-12-name">
<svg aria-hidden="true">...</svg>
<span id="row-12-name">Q4 reports</span>
<button>Rename</button>
</li>Applies to role="menuitem", role="menuitemcheckbox", and role="menuitemradio". Each menu command needs a name announcing what it does. Visible text inside the item is best. Icon-only menu items must use aria-label.
<!-- BAD: icon-only menuitem -->
<li role="menuitem">
<svg aria-hidden="true">...</svg>
</li>
<!-- GOOD: visible text -->
<li role="menuitem">Duplicate</li>
<!-- GOOD: icon-only with aria-label -->
<li role="menuitem" aria-label="Duplicate">
<svg aria-hidden="true">...</svg>
</li>
<!-- GOOD: menuitemcheckbox with state and name -->
<li role="menuitemcheckbox" aria-checked="true">Show ruler</li>All six naming audits, with the most common bad version and the simplest good version:
| Audit | Bad (fails) | Good (passes) |
|---|---|---|
aria-dialog-name | <div role="dialog"> | <div role="dialog" aria-labelledby="h"> |
aria-tooltip-name | <div role="tooltip"></div> | <div role="tooltip">Saves draft</div> |
aria-progressbar-name | <div role="progressbar"> | <div role="progressbar" aria-label="Upload"> |
aria-meter-name | <div role="meter"> | <div role="meter" aria-label="Disk usage"> |
aria-treeitem-name | <li role="treeitem"><svg/></li> | <li role="treeitem">Q4 reports</li> |
aria-command-name | <li role="menuitem"><svg/></li> | <li role="menuitem" aria-label="Duplicate"><svg/></li> |
aria-labelText inside the element is the strongest name source — sighted users and screen-reader users see and hear the same string. aria-label is invisible, so it is the easiest place for label drift (the visible UI says one thing; the screen reader announces another).
aria-labelledby when the label already exists on the pageA modal's heading, a status line above a progress bar, the name column in a tree row — all of these are existing visible text. Reference them with aria-labelledby="id" instead of duplicating the string in aria-label.
Icons inside a widget should carry aria-hidden="true" (or be CSS-only) so they do not interfere with name computation. A decorative <svg> with stray <title> text can override your intended name.
Aim for 1–4 words. "Delete account," "Uploading attachment," "Disk usage." A 30-word sentence in aria-label is fatiguing on every focus event.
Screen readers already announce the role. aria-label="Confirm dialog" on a role="dialog"becomes "Confirm dialog dialog." Drop the redundancy — just "Confirm."
aria-labelledby="dialog-title" pointing at a missing id is silently ignored. The widget ends up unnamed. Lint rules like the jsx-a11y/aria-props family help catch this; a Greadme deep scan flags every dangling reference.
A multi-step modal that reuses the same role="dialog" for every step must update the heading (and therefore the name) when the step changes. A stale name is almost as bad as a missing one.
menuitemcheckbox needs aria-checked; progressbar needs aria-valuenow; treeitem in an expandable tree needs aria-expanded. The name explains what the widget is; state attributes explain what it is doing.
eslint-plugin-jsx-a11y rules including jsx-a11y/role-has-required-aria-props and jsx-a11y/no-noninteractive-element-to-interactive-role catch many missing-name patterns before merge.
Automated checks catch missing names; only a human ear catches awkward names. Tab into every widget with NVDA, VoiceOver, or TalkBack and listen to what gets announced. If you would not understand it cold, fix the name.
What's happening: The tooltip <div role="tooltip"> is rendered empty and a script fills it on hover. Static analyzers see an empty element and flag aria-tooltip-name.
Fix: Render the tooltip text in the markup at build time, even if the tooltip is hidden by CSS. Server-rendered content also helps Google AI Overviews and other AI engines that may not execute your JavaScript.
What's happening: Three modals on a page all use aria-labelledby="dialog-title". Two of them point at the wrong element.
Fix: Generate unique IDs per instance — React's useId(), Vue's useId(), or any component-scoped ID hook.
What's happening: Focus enters the inner <button> instead of the treeitem row. The treeitem itself has no name.
Fix: Either restructure so the row is the focusable element with text content, or add aria-labelledby on the treeitem pointing at the row's name.
What's happening: Many mobile screen readers do not surface title. The menu item has no accessible name.
Fix: Replace title with aria-label on the menu item.
It verifies that every element with role="dialog" or role="alertdialog" (and the native <dialog> element) has an accessible name. The most common pass is aria-labelledbypointing at the dialog's heading.
You can, but aria-labelledby wins. The Accessible Name and Description Computation algorithm treats aria-labelledby as a higher-priority source than aria-label. To avoid confusion, choose one.
Yes. The role is implicit, but the name still has to come from somewhere — visible heading, <label> association, aria-labelledby, or aria-label. Native semantics do not invent a name for you.
Yes — Success Criterion 4.1.2 Name, Role, Value (Level A). It is also one of the most-cited failures in WebAIM's annual home-page audits and in US ADA / EU European Accessibility Act litigation.
The HTML title attribute is unreliable as an accessible name source — many mobile screen readers do not surface it, and accessibility checkers do not count it as a guaranteed name. Use aria-label instead, or include visible text.
A progressbar represents a process moving toward completion (file upload, multi-step form). A meter represents a static measurement at a point in time (disk usage, password strength). They have separate audits — aria-progressbar-name and aria-meter-name — but the naming rules are identical.
Indirectly. AI answer engines parse the accessibility tree and semantic HTML to understand the structure of your pages. Widgets with no accessible name produce a noisier tree, weaker structured signals, and worse rankings in traditional Google search — and AI answer engines preferentially cite pages that already rank well.
All six audits — aria-dialog-name, aria-tooltip-name, aria-progressbar-name, aria-meter-name, aria-treeitem-name, and aria-command-name — fail for the same root reason: the widget has a role but no name. Fix them in the same priority order every time: visible text first, aria-labelledby at existing labels next, aria-label only when nothing visible is available.
Run a Greadme deep scan to see exactly which widgets on your site are unnamed, which roles they are using, and which template is responsible — then fix the component once and clear every instance at the same time.