Who it affects
Someone using a screen reader hovers or focuses the info icon, hears tooltip, and gets none of the explanation sighted people are reading.
Someone using screen magnification sees only part of the tooltip and relies on the screen reader to fill in the rest, which cannot.
Someone using voice control cannot refer to the tooltip by its content when asking for it to be read.
Broken versus fixed
Both examples are live and rendered in a sandboxed frame. The broken one is deliberately broken.
The tooltip is on screen and reads fine. A screen reader on the info button announces: What is CVC?, button, and then nothing, because the tooltip's text is aria-hidden so it has no name to pass on.
Announced: What is CVC?, button, The 3 digits on the back of your card. The tooltip's text is its name, and aria-describedby on the button reads it out. (The demo keeps the tooltip permanently shown; real ones appear on hover and focus.)
Show the markup behind both examples
<style>body{font-family:system-ui;padding:16px;margin:0}.row{display:flex;align-items:center;gap:6px;position:relative;width:max-content}label{font-size:14px}.i{width:18px;height:18px;border-radius:50%;border:0;background:#2b6cb0;color:#fff;font-size:12px;font-weight:700;cursor:default}.tip{position:absolute;left:0;top:28px;background:#2d3748;color:#fff;font-size:13px;padding:6px 10px;border-radius:6px;width:220px}input{display:block;margin-top:34px;width:80px}</style>
<div class="row">
<label for="cvc">CVC</label>
<button class="i" type="button" aria-label="What is CVC?" aria-describedby="tip">?</button>
<div class="tip" role="tooltip" id="tip"><span aria-hidden="true">The 3 digits on the back of your card</span></div>
</div>
<input id="cvc" inputmode="numeric" maxlength="4">
<style>body{font-family:system-ui;padding:16px;margin:0}.row{display:flex;align-items:center;gap:6px;position:relative;width:max-content}label{font-size:14px}.i{width:18px;height:18px;border-radius:50%;border:0;background:#2b6cb0;color:#fff;font-size:12px;font-weight:700;cursor:default}.tip{position:absolute;left:0;top:28px;background:#2d3748;color:#fff;font-size:13px;padding:6px 10px;border-radius:6px;width:220px}input{display:block;margin-top:34px;width:80px}</style>
<div class="row">
<label for="cvc">CVC</label>
<button class="i" type="button" aria-label="What is CVC?" aria-describedby="tip">?</button>
<div class="tip" role="tooltip" id="tip">The 3 digits on the back of your card</div>
</div>
<input id="cvc" inputmode="numeric" maxlength="4">
Test it by hand in two minutes
- Search the page source for role="tooltip". Each one should contain plain text, or have an aria-label or aria-labelledby.
- Check nothing inside the tooltip is aria-hidden, and that the tooltip is not hidden with display: none at the moment it is supposed to be read.
- Check the trigger (the icon or field) points at the tooltip with aria-describedby, so the text is read when the trigger gets focus.
- Turn on VoiceOver (Cmd+F5) or NVDA and Tab to the trigger. After its name you should hear the tooltip text. If you only hear the word tooltip, the name is missing.
The fix
<div role="tooltip" id="tip"><span aria-hidden="true">The 3 digits on the back of your card</span></div>
<div role="tooltip" id="tip">The 3 digits on the back of your card</div>
Let the text be the name. If the tooltip holds only an icon, add aria-label with the same meaning. And give the trigger aria-describedby="tip" so the tooltip is read in context.
Tooltip that is only an icon
<div role="tooltip" id="lock"><svg>...</svg></div>
<div role="tooltip" id="lock" aria-label="Your details are encrypted"><svg aria-hidden="true">...</svg></div>
An icon does not name anything. Say in words what the icon means.
Tooltip not linked to its trigger
<button aria-label="Help">?</button>
<div role="tooltip" id="tip">Enter the code we emailed you</div>
<button aria-label="Help" aria-describedby="tip">?</button>
<div role="tooltip" id="tip">Enter the code we emailed you</div>
The name check passes without this, but the text is only useful if the trigger references it.
Common mistakes
- Hiding the tooltip text with aria-hidden to stop it being read twice, which stops it being read at all.
- Putting the tooltip text in a title attribute on the icon and the role="tooltip" on an empty div.
- Using role="tooltip" for a rich popover with links and buttons; a tooltip is plain text only.
- Showing the tooltip on hover only, so keyboard users never trigger it.
- Naming the tooltip tooltip or info.
WCAG criteria this rule maps to
axe-core tags this rule as WCAG 2.2 level A. It is one of the checks for:
- 4.1.2 Name, Role, Value (level A). Official W3C text.
The scanner can see a tooltip whose computed name is empty. It cannot tell whether the trigger is wired to it, or whether it appears on keyboard focus, so check both by hand. Automated tools find only part of the accessibility problems on a page; a pass on this rule is not a pass on the criterion.
Related rules
aria-prohibited-attr
aria-label and aria-labelledby only work on elements that can have a name: controls, landmarks, images, headings, tables and similar.
A
ARIA attributes must have valid values
aria-valid-attr-value
Each ARIA attribute accepts a particular kind of value: true or false, a number, a word from a short list, or the id of another element.
A
Buttons must have a name that says what they do
button-name
A button needs a name that assistive technology can announce.
A
Hidden elements must not contain anything you can Tab to
aria-hidden-focus
aria-hidden="true" tells screen readers to ignore an element, but it does not stop the Tab key reaching the links and buttons inside.