Content that appears on hover or focus must be dismissible, hoverable and persistent. See how to build help icons and preview cards that pass.
When pointer hover or keyboard focus makes extra content appear and later disappear, three things must be true. The content must be dismissible without moving the pointer or focus, hoverable so the pointer can travel onto it without it vanishing, and persistent, staying until the trigger is removed, the user dismisses it, or its information is no longer valid.
Two exceptions are worth knowing. Content drawn by the browser from an attribute such as title is the browser's job, not yours, as long as you have not restyled it. And the dismissible rule does not apply when the content communicates an input error or does not cover or replace other content.
Think of a screen magnifier user. At 400 percent, a tooltip may sit partly off screen; to read it they must move the pointer onto it. If it disappears as soon as the pointer leaves the icon, it can never be read.
Hover content on government sites tends to be help icons beside fee fields, library catalog preview cards, map pin details and navigation flyouts. Each is a small convenience that becomes a barrier when it cannot be reached with a keyboard, when it covers the label of the next field, or when it vanishes before a magnifier user has read it.
The fix is rarely complicated. Most failures come from building the pop-up on CSS :hover alone and never thinking about focus, Escape or the gap between trigger and content.
A pseudo-element tooltip built from a data- attribute appears on hover only, cannot be reached by keyboard, and has no way to close.
<span class="tip" data-tip="Fees are due at filing">?</span>
.tip:hover::after { content: attr(data-tip); position: absolute; }<button type="button" class="tip" aria-describedby="fee-tip">?
<span class="visually-hidden">Filing fee help</span></button>
<div id="fee-tip" role="tooltip" hidden>Fees are due at filing.</div>
<script>
const btn = document.querySelector(".tip");
const tip = document.getElementById("fee-tip");
let timer;
const show = () => { clearTimeout(timer); tip.hidden = false; };
const hide = () => tip.hidden = true;
const hideSoon = () => { timer = setTimeout(hide, 200); };
btn.addEventListener("focus", show);
btn.addEventListener("blur", hide);
btn.addEventListener("mouseenter", show);
btn.addEventListener("mouseleave", hideSoon);
tip.addEventListener("mouseenter", show);
tip.addEventListener("mouseleave", hideSoon);
document.addEventListener("keydown", e => { if (e.key === "Escape") hide(); });
</script>A catalog preview card that hides the instant the pointer leaves the title cannot be reached. A short grace period lets the pointer travel in, and entering the card cancels the hide.
title.addEventListener("mouseleave", () => card.hidden = true);let timer;
const open = () => { clearTimeout(timer); card.hidden = false; };
const close = () => { timer = setTimeout(() => card.hidden = true, 200); };
title.addEventListener("mouseenter", open);
title.addEventListener("focus", open);
title.addEventListener("blur", close);
title.addEventListener("mouseleave", close);
card.addEventListener("mouseenter", open);
card.addEventListener("mouseleave", close);
document.addEventListener("keydown", e => { if (e.key === "Escape") card.hidden = true; });A hint that appears on focus and sits on top of the next label hides what the user needs to read, and the only way to dismiss it is to leave the field. Keep it in the flow so nothing is covered.
.parcel-hint {
position: absolute;
top: 100%;
background: #fff;
box-shadow: 0 2px 8px rgba(0,0,0,.3);
}
input:focus + .parcel-hint { display: block; }.parcel-hint { display: none; margin-top: 0.25rem; }
input:focus + .parcel-hint { display: block; }
/* in the flow: the next field moves down instead of being covered */On touch-first screens there is no hover, so the criterion mostly does not come into play. Where an app is used with a mouse or trackpad, for example on a tablet with a pointer, prefer the platform's own tooltip and help components over a custom overlay, and keep any overlay you build dismissible, hoverable and persistent.
:hover only and no focus handling.mouseleave of the trigger without considering the content.title tooltip with a script and losing the exception.Ship it when:
Keyboard access itself is a separate criterion, and the hover rules sit on top of it. For overlays built by a vendor, see vendors and third-party content.