# Tooltips that vanish on approach and can't be dismissed

Source: https://easeweb.dev/learn/hover-content
Topics: Dialogs and overlays > Tooltips and hover content can be reached and dismissed (WCAG 1.4.13)
The fix: Either keep a hover tooltip open while the pointer is on the trigger or the pop-up text, hide it on Escape, and drop any timer. Or turn it into a toggletip: a button that opens the pop-up text as a native popover on click, which the browser closes on Escape, and copies the text into a status region so screen readers read it when it opens.
Test with: mouse, keyboard, zoom, screen reader
References: https://www.w3.org/WAI/WCAG22/Understanding/content-on-hover-or-focus.html https://www.w3.org/WAI/ARIA/apg/patterns/tooltip/ https://developer.mozilla.org/en-US/docs/Web/API/Popover_API https://inclusive-components.design/tooltips-toggletips/

This is a recreated teaching example, not a finding about a named client. The code is a starting pattern: test it in your own product. No completed assistive-technology test is claimed here.

## What happens

A checkout summary lists the delivery fee with a small ? button beside it. Point at the button and a dark box of pop-up text appears a little below: "Free on orders over $50. Otherwise $6 to most addresses." Move the pointer down to read it, and it disappears the moment the pointer leaves the button. Tab to the button and the pop-up text appears again, this time on top of the coupon code field. Press Escape and nothing happens; the only way to uncover the field is to move focus away from the button.

## Why it fails

The pop-up text is shown by a CSS rule on the button's own `:hover` and `:focus`, and it sits 8 pixels below the button. The pointer has to cross that gap to reach the pop-up text, and the button stops being hovered on the way, so the pop-up text closes: it is not hoverable. Nothing listens for Escape, so pop-up text that covers other content can't be dismissed without moving focus or the pointer. Success criterion 1.4.13 asks for both, and for the pop-up text to stay until the user dismisses it or moves away, which this one does.

## Who is affected

The failure is worst at high zoom, where the pop-up text may open partly off screen and the only way to see the rest is to move toward it.

## What automation and AI miss

The button has a name and the pop-up text is ordinary text, so a static scan finds nothing. The failure only shows when someone moves the pointer from the button toward the pop-up text, or presses Escape while it is open. Sibling selectors like `.trigger:hover + .note` are the usual answer to "show a tooltip on hover", and AI coding tools write them with a margin for breathing room, which is exactly the gap that closes the pop-up text.

## Before: pop-up text shown by the button's own hover

```html
<p class="fee">
  Delivery fee
  <button type="button" class="tip-trigger" aria-describedby="fee-tip" aria-label="Delivery fee details">?</button>
  <span role="tooltip" id="fee-tip" class="tip-note">Free on orders over $50. Otherwise $6 to most addresses.</span>
</p>
<label for="coupon">Coupon code</label>
<input id="coupon">
```

```css
.fee { position: relative; }
.tip-note { display: none; position: absolute; top: calc(100% + 8px); left: 0; }
.tip-trigger:hover + .tip-note,
.tip-trigger:focus + .tip-note { display: block; }
```

The pop-up text belongs to the button's hover alone, so crossing the 8-pixel gap closes it. Escape does nothing, and the pop-up text covers the coupon field until focus moves on. The markup is already a correct tooltip, with `role="tooltip"` and `aria-describedby`: the role tells a screen reader what the pop-up text is, but it adds no behaviour, so the failure stays.

## After: a hover tooltip that stays and hides on Escape

```html
<p class="fee">
  Delivery fee
  <span class="tip">
    <button type="button" aria-label="Delivery fee details" aria-describedby="fee-tip">?</button>
    <span role="tooltip" id="fee-tip" class="tip-note" hidden>Free on orders over $50. Otherwise $6 to most addresses.</span>
  </span>
</p>
```

```css
.tip { position: relative; }
.tip-note { position: absolute; top: calc(100% + 8px); left: 0; }
/* An invisible bridge over the gap, so the pointer stays inside .tip on its way down. */
.tip-note::before { content: ''; position: absolute; inset: -8px 0 100% 0; }
```

```js
const tip = document.querySelector('.tip');
const note = tip.querySelector('[role="tooltip"]');
let dismissed = false;
function update() {
  const wanted = tip.matches(':hover') || tip.matches(':focus-within');
  if (!wanted) dismissed = false;
  note.hidden = !wanted || dismissed;
}
for (const type of ['pointerenter', 'pointerleave', 'focusin', 'focusout']) {
  tip.addEventListener(type, () => requestAnimationFrame(update));
}
document.addEventListener('keydown', (event) => {
  if (event.key === 'Escape' && !note.hidden) {
    dismissed = true;
    update();
  }
});
```

The pop-up text is inside `.tip`, and its bridge covers the gap, so the pointer stays on the component all the way from the button to the pop-up text. It closes only when both hover and focus have left, with no timer. Escape hides it without moving anything, and it stays hidden until the user leaves and comes back. `aria-describedby` lets a screen reader read the pop-up text with the button even while it is hidden.

## After: a toggletip that opens on click

```html
<p class="fee">
  Delivery fee
  <button type="button" popovertarget="fee-tip" aria-label="Delivery fee details">?</button>
  <span role="status" id="fee-tip-status" class="visually-hidden"></span>
</p>
<div id="fee-tip" popover>Free on orders over $50. Otherwise $6 to most addresses.</div>
```

```js
const tip = document.getElementById('fee-tip');
const status = document.getElementById('fee-tip-status');
// Copy the text into the status region when it opens, so screen readers read it straight away.
tip.addEventListener('toggle', (event) => {
  status.textContent = event.newState === 'open' ? tip.textContent : '';
});
```

The pop-up text no longer appears on hover or focus at all. It opens when the button is clicked, tapped or pressed with Enter or Space, and stays until the user closes it. The browser closes the popover on Escape or a click outside, returns focus to the button when focus was inside, and tells screen readers whether it is open. On its own, a screen reader would only say "expanded" on the press, and the user would have to move on to find the text. The few lines of script copy the text into an empty `role="status"` region beside the button when the popover opens, so it is read straight away, and clear it on close so the next press reads it again. The region must be in the page, empty, before the first press, and `visually-hidden` stands for your usual class that hides text from sight but not from screen readers. It works the same on touch screens, where hover doesn't exist. Like any popover it sits in the top layer, so you place it next to the button with CSS anchor positioning or a few lines of script.

## Which option to use

Both pass. The hover tooltip keeps the pop-up text one glance away for mouse users and works in every browser, but the script, the bridge and the Escape handling are yours to maintain, and on a touch screen the pop-up text still depends on focus. With the hover tooltip, a screen reader reads the pop-up text as soon as the button has focus, through `aria-describedby`. The toggletip leaves the browser to open, close and return focus, and needs only the short script that announces the text; it behaves the same for mouse, keyboard and touch, at the cost of a press before the pop-up text shows or is read. Choose the hover tooltip when the pop-up text is a short label people expect to see by pointing, as on icon-only toolbar buttons. Choose the toggletip when the pop-up text is information people need to read, such as fees, rules or password requirements.

## Implementation decisions

CSS alone can make pop-up text hoverable: `.tip:hover .tip-note, .tip:focus-within .tip-note` keeps it open on the pop-up text as well as the button. It is not a fix on its own, because Escape still can't hide it; add the keydown handler. Escape is required only when the pop-up text covers or replaces other content. Pop-up text placed where it hides nothing may skip it, but layouts change at zoom, so it is safer to support Escape everywhere.

`role="tooltip"` belongs only on text that appears on hover or focus and describes its trigger, and the browser does nothing with it but announce it. It is the hover tooltip's role. The toggletip leaves it out, because its pop-up text opens on click and the button already says whether it is open.

Don't use the `title` attribute for anything people need. The browser draws that tooltip, so 1.4.13 doesn't apply to it, but it never appears on keyboard focus or touch, and it can't be styled or reached. Never hide pop-up text on a timer: a slow reader loses it, and the criterion asks for it to stay.

Keep the pop-up text short and free of links or buttons. Interactive content belongs in a non-modal dialog or a disclosure, as in `panel-dismissal`, not in a tooltip. If your component library supplies a tooltip, test the three checks below on it; many open on the trigger's hover alone and close on a short delay.

## Verify the fix

1. Point at the ? button, then move the pointer slowly onto the pop-up text and around it; the pop-up text stays open the whole time.
2. With the pop-up text open, press Escape; it hides, and the pointer and focus stay where they were.
3. Leave the pop-up text open for a minute without moving; it stays.
4. Tab to the button (hover tooltip) or press Enter on it (toggletip); the pop-up text shows, and Escape hides it with focus still on the button.
5. At 200% zoom, and with a screen reader, confirm all of the pop-up text can be reached and is read: with the button for the hover tooltip, and as soon as it opens for the toggletip.

Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after the shared tooltip component changes.

## Limits

This covers a short piece of pop-up text tied to one button. It doesn't cover menus, dialogs or pop-ups with links inside them, or content that appears on scroll or after a delay. Test your real tooltips at the zoom levels and with the assistive technology your users have.
