Skip to content

Tooltips need text

  • Rule: axe/aria-tooltip-name
  • Checks: ARIA tooltip nodes must have an accessible name
  • WCAG: 4.1.2
  • Fix class: content: A fix needs authored text.
  • People who use a screen reader or a braille display, most of them blind.

The tooltip stays on the page, empty, until it opens

Section titled “The tooltip stays on the page, empty, until it opens”

Hide the tooltip with the hidden attribute or display: none while it is closed, and fill it with text before you show it. A tooltip hidden only with opacity or moved off screen is still in the accessibility tree. Link it from its trigger with aria-describedby.

<button type="button" aria-describedby="copy-tip">Copy link</button>
<div role="tooltip" id="copy-tip" hidden>Copies the page address to your clipboard</div>

Put the words in the tooltip as text, or give an image inside it an alt that says them.

axe accepts it, but sighted users still see an empty box when the tooltip opens. Put the text in the tooltip itself, where everyone gets it.

The part of the fix that differs in React, in its syntax. The rest of the guidance holds.

A tooltip component keeps an empty role="tooltip" element mounted and fills it only when it opens

Section titled “A tooltip component keeps an empty role="tooltip" element mounted and fills it only when it opens”

Render the tooltip’s text from the start and toggle hidden={!open}, so the element is hidden while closed and the trigger’s aria-describedby, with an id from useId, always finds it. Keep it open while the trigger has focus or the pointer is over the trigger or the tooltip, and close it on Escape.

import { useEffect, useId, useState } from 'react';
function CopyLinkButton() {
const tipId = useId();
const [focused, setFocused] = useState(false);
const [hovered, setHovered] = useState(false);
const [dismissed, setDismissed] = useState(false);
const open = (focused || hovered) && !dismissed;
useEffect(() => {
if (!open) return;
const onKeyDown = (event) => event.key === 'Escape' && setDismissed(true);
document.addEventListener('keydown', onKeyDown);
return () => document.removeEventListener('keydown', onKeyDown);
}, [open]);
return (
<span
onMouseEnter={() => {
setHovered(true);
setDismissed(false);
}}
onMouseLeave={() => setHovered(false)}
>
<button
type="button"
aria-describedby={tipId}
onFocus={() => {
setFocused(true);
setDismissed(false);
}}
onBlur={() => setFocused(false)}
>
Copy link
</button>
<span role="tooltip" id={tipId} hidden={!open}>
Copies the page address to your clipboard
</span>
</span>
);
}

Mounting the tooltip only while it is open, with the trigger always pointing at it

Section titled “Mounting the tooltip only while it is open, with the trigger always pointing at it”

While the tooltip is closed, aria-describedby names an element that is not in the page, and when it opens on focus, screen readers may already have announced the trigger without it.

axe-core’s documentation of the rule

CORA’s fix guidance adapts content from accessibility-agents, under the MIT License: see its notice.