How to Give ARIA Widgets Accessible Names? Complete Guide (2026)

Saar Twito10 min read
Saar Twito
Saar TwitoFounder & SEO Engineer

Hi, I'm Saar - a software engineer, SEO specialist, and lecturer who loves building tools and teaching tech.

View author profile →

What Is an Accessible Name on an ARIA Widget?

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.

Key Facts (TL;DR)

  • WCAG criterion: Success Criterion 4.1.2 Name, Role, Value (Level A). A role with no name fails this check on every assistive-tech engine.
  • Six audits, one fix family: 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.
  • Three ways to provide a name (in priority order): visible text inside the element, aria-labelledby pointing at visible text, or aria-label as a last resort.
  • Why it's common: WebAIM's 2025 Million homepage analysis found that 57.5% of home pages had at least one form/control with a missing accessible name, making it the most-failed Level A criterion category on the web.
  • Native alternative first: Native <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.

Why Accessible Names on Widgets Matter

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.

  • Dialogs become "dialog":A modal that opens with no name is announced as "dialog" with no further information. The user has no idea whether it is a confirmation, a sign-up form, or an error.
  • Progress bars stop reporting purpose: A bar at 65% is meaningless without context. Is it a file upload? A multi-step form? A video buffer? The accessible name carries that meaning.
  • Trees become unnavigable: Tree widgets (file explorers, nested category pickers) rely on each treeitem being announced by name. Unnamed items collapse the entire structure into a featureless list.
  • Menu items become silent: A menuitem with no text content and no aria-labelis announced as just "menu item," making keyboard navigation through a menu impossible.
  • WCAG 4.1.2 violations: Name, Role, Value is Level A — the most basic conformance bar. Missing names are also frequently cited in US ADA and EU European Accessibility Act (in force June 2025) lawsuits.
  • AI search visibility: AI answer engines parse the accessibility tree to understand interactive structure on a page. Widgets without names degrade the structured signals that platforms like Google AI Overviews and Perplexity use when picking citations.

The Three Ways to Name a Widget

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:

1. Visible text content (preferred)

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.

2. aria-labelledby pointing at visible text elsewhere

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

3. aria-label as a fallback

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

How to Fix Each of the Six Audits

aria-dialog-name — Dialogs Missing Accessible Names

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>

aria-tooltip-name — Tooltips Missing Accessible Names

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>

aria-progressbar-name — Progress Bars Missing Labels

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>

aria-meter-name — Meter Elements Missing Labels

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>

aria-treeitem-name — Tree Items Missing Labels

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>

aria-command-name — Menu Items Missing Labels

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>

How to Check Your Pages

  • Greadme deep scan — runs all six naming audits, lists each unnamed widget with the role it is using, and offers a one-click GitHub PR fix where applicable.
  • Greadme crawler scan — finds repeated naming bugs across templates (a single broken modal component will fail on every page that mounts it).
  • Greadme AI visibility analyzer — surfaces how clean accessible names affect how AI answer engines interpret interactive structure on your pages.
  • Chrome DevTools → Accessibility tab — select any element and look at the "Name" field. If it is empty for a widget that needs one, the audit will fail.
  • axe-core (the open-source rules engine) — integrates into Jest, Playwright, and Cypress so CI fails on any unnamed widget before merge.
  • Manual screen-reader test — open the page in NVDA (Windows), VoiceOver (macOS/iOS), or TalkBack (Android). If a widget is announced by role only, it has no accessible name.

Bad vs Good: Side-by-Side Examples

All six naming audits, with the most common bad version and the simplest good version:

AuditBad (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>

10 Rules for Reliable Accessible Names

1. Prefer visible text over aria-label

Text 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).

2. Use aria-labelledby when the label already exists on the page

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

3. Hide decorative icons from the accessibility tree

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.

4. Keep names short and specific

Aim for 1–4 words. "Delete account," "Uploading attachment," "Disk usage." A 30-word sentence in aria-label is fatiguing on every focus event.

5. Don't repeat the role in the name

Screen readers already announce the role. aria-label="Confirm dialog" on a role="dialog"becomes "Confirm dialog dialog." Drop the redundancy — just "Confirm."

6. Verify ID references resolve

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.

7. Update the name when the widget changes purpose

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.

8. Pair the name with state on stateful widgets

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.

9. Lint at build time

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.

10. Test with a real screen reader

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.

Common Mistakes and How to Avoid Them

Problem: Empty tooltip element with text injected later

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.

Problem: Dialog reuses a single ID across instances

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.

Problem: Treeitem text is wrapped in a button that takes focus

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.

Problem: Icon-only menu item with title attribute only

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.

FAQ

What does the aria-dialog-name audit check?

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.

Can I use aria-label and aria-labelledby together?

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.

Does a native <dialog> or <progress> element still need an explicit name?

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.

Is a missing accessible name a WCAG failure?

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.

Why does my icon-only menuitem fail aria-command-name even with a title?

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.

What is the difference between progressbar and meter?

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.

Do unnamed widgets affect AI search engines like Perplexity and ChatGPT?

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.

Conclusion

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.