ARIA Rule

Tabs must have an accessible name aria-tab-name

Every element with role="tab" needs a name: the words a screen reader speaks when the tab gets focus and the words a voice control user says to pick it. Tabs built from text get their name for free. Tabs built from icons alone, or from an image with no alt, have no name, so the listener hears a count but never which tab is which.

Websight reports this as ARIA tab nodes must have an accessible name

Who it affects

Someone using a screen reader arrows across the tab bar and hears tab, 1 of 3, selected; tab, 2 of 3; tab, 3 of 3. They have to open each one to find out what it is.

Someone using voice control cannot say the tab's name to switch to it and has to fall back to showing numbered overlays.

Someone with low vision using a screen reader for support can see the icons but cannot confirm what they mean, and the screen reader cannot tell them.

Broken versus fixed

Both examples are live and rendered in a sandboxed frame. The broken one is deliberately broken.

Broken

Three tidy icon tabs. A screen reader announces: tab, 1 of 3, selected; tab, 2 of 3; tab, 3 of 3. Which one is notifications? No way to tell.

Fixed

Announced: Home, tab, 1 of 3, selected; Notifications, tab, 2 of 3; Settings, tab, 3 of 3. Switching tabs still needs script; the demo is static.

Show the markup behind both examples
Broken
<style>body{font-family:system-ui;padding:16px;margin:0}.tl{display:flex;gap:4px;border-bottom:1px solid #cbd5e0}.tl button{border:0;background:none;padding:8px 14px;border-bottom:2px solid transparent;cursor:pointer}.tl [aria-selected=true]{border-bottom-color:#2b6cb0}.pn{padding:12px 4px;font-size:14px}svg{display:block}</style>
<div class="tl" role="tablist">
  <button role="tab" aria-selected="true" aria-controls="p1"><svg width="18" height="18" viewBox="0 0 18 18"><path d="M2 9l7-6 7 6v7H2z" fill="#2d3748"/></svg></button>
  <button role="tab" aria-selected="false" tabindex="-1"><svg width="18" height="18" viewBox="0 0 18 18"><path d="M9 2a5 5 0 00-5 5v4l-2 2h14l-2-2V7a5 5 0 00-5-5zm0 15a2 2 0 002-2H7a2 2 0 002 2z" fill="#2d3748"/></svg></button>
  <button role="tab" aria-selected="false" tabindex="-1"><svg width="18" height="18" viewBox="0 0 18 18"><circle cx="9" cy="9" r="3" fill="none" stroke="#2d3748" stroke-width="2"/><circle cx="9" cy="9" r="7" fill="none" stroke="#2d3748" stroke-width="2" stroke-dasharray="3 2"/></svg></button>
</div>
<div class="pn" role="tabpanel" id="p1">Welcome back. You have 2 new messages.</div>
Fixed
<style>body{font-family:system-ui;padding:16px;margin:0}.tl{display:flex;gap:4px;border-bottom:1px solid #cbd5e0}.tl button{border:0;background:none;padding:8px 14px;border-bottom:2px solid transparent;cursor:pointer}.tl [aria-selected=true]{border-bottom-color:#2b6cb0}.pn{padding:12px 4px;font-size:14px}svg{display:block}</style>
<div class="tl" role="tablist" aria-label="Account">
  <button role="tab" aria-selected="true" aria-controls="p1" aria-label="Home"><svg aria-hidden="true" width="18" height="18" viewBox="0 0 18 18"><path d="M2 9l7-6 7 6v7H2z" fill="#2d3748"/></svg></button>
  <button role="tab" aria-selected="false" tabindex="-1" aria-label="Notifications"><svg aria-hidden="true" width="18" height="18" viewBox="0 0 18 18"><path d="M9 2a5 5 0 00-5 5v4l-2 2h14l-2-2V7a5 5 0 00-5-5zm0 15a2 2 0 002-2H7a2 2 0 002 2z" fill="#2d3748"/></svg></button>
  <button role="tab" aria-selected="false" tabindex="-1" aria-label="Settings"><svg aria-hidden="true" width="18" height="18" viewBox="0 0 18 18"><circle cx="9" cy="9" r="3" fill="none" stroke="#2d3748" stroke-width="2"/><circle cx="9" cy="9" r="7" fill="none" stroke="#2d3748" stroke-width="2" stroke-dasharray="3 2"/></svg></button>
</div>
<div class="pn" role="tabpanel" id="p1" aria-labelledby="t1">Welcome back. You have 2 new messages.</div>

Test it by hand in two minutes

  1. Inspect each element with role="tab". It needs visible text inside it, an aria-label, an aria-labelledby, or an image with alt text.
  2. In Chrome DevTools, select the tab and open the Accessibility pane. The computed Name should be a word, not empty.
  3. Turn on VoiceOver (Cmd+F5) or NVDA, Tab to the tab bar and press the Left and Right arrows. Each tab should be announced with a name before the word tab.
  4. With voice control (Voice Control on a Mac, Voice Access on Windows), say click followed by the tab's name. The right tab should respond.

The fix

Before
<button role="tab" aria-selected="true"><svg>...</svg></button>
After
<button role="tab" aria-selected="true" aria-label="Notifications"><svg aria-hidden="true">...</svg></button>

Name the tab for what is in its panel, in one or two words. Visible text is better than aria-label where the design allows it, because everyone benefits.

Tab with an icon image

Before
<div role="tab"><img src="bell.svg"></div>
After
<div role="tab" tabindex="0"><img src="bell.svg" alt="Notifications"></div>

When the image is the only content, its alt text becomes the tab's name.

Icon tab with hidden text

Before
<button role="tab"><svg>...</svg></button>
After
<button role="tab"><svg aria-hidden="true">...</svg><span class="visually-hidden">Settings</span></button>

A visually hidden span keeps the text in the page for screen readers and voice control. Use a CSS class that hides it off screen, not display: none.

Common mistakes

  • Putting a title attribute on the svg and expecting it to name the tab; it rarely does.
  • Naming the tab with aria-label on the tablist, which names the whole bar, not each tab.
  • Using aria-label="tab 1", which is a position, not a name.
  • Giving the name only on the tabpanel and leaving the tab itself blank.
  • Hiding the text label with display: none, which removes it from screen readers too.

WCAG criteria this rule maps to

axe-core tags this rule as WCAG 2.2 level A. It is one of the checks for:

The scanner can see a tab whose computed name is empty. It cannot judge whether the name describes the panel it opens, so read the names next to the panels. Automated tools find only part of the accessibility problems on a page; a pass on this rule is not a pass on the criterion.

Scan your own page for this and 63 other checks

Open any page in Chrome, click Websight, and get a score out of 100 with every issue explained and a draft fix snippet per element.

Add Websight to Chrome

Free scans, no account, nothing leaves your computer. Automated tools find only part of the problems; the extension says so on every report.