Understanding WCAG 1.4.13
What is it?
WCAG 1.4.13 Content on Hover or Focus is a Level AA criterion, added in WCAG 2.1.
When hovering or focusing something makes extra content appear, that content has to be dismissible, hoverable, and persistent. Tooltips, sub-menus, and popovers all count.
Why it matters
Content that appears on hover is easy to build and easy to get wrong.
It can vanish before you’ve finished reading. It can sit on top of the thing you were trying to read. It can refuse to let you move your pointer onto it. The information is there, and you still can’t get to it.
None of these are exotic bugs. They come from ordinary CSS and ordinary JavaScript doing exactly what they were told, on patterns that look fine when you build them and fail the moment someone interacts with them differently than you did.
Who it affects
Anyone can lose a tooltip on the way to it. For some people it happens every time.
People who are…
- using screen magnification, where the trigger and the tooltip may not fit on screen together
- moving a pointer with limited fine motor control
- reading slowly, or needing a second pass
- navigating by keyboard, where the content has to appear on focus as well as hover
The three rules
Dismissible. You can get rid of it without moving your pointer or your focus.
Hoverable. You can move the pointer onto the content itself without it disappearing.
Persistent. It stays until hover or focus is removed, you dismiss it, or the information stops being valid.
Common failures
- A gap between the trigger and the tooltip. The tooltip sits a few pixels below the icon. Move toward it, the pointer crosses dead space, and it’s gone. The pointer is briefly over neither the trigger nor the tooltip, hover is lost, and the tooltip hides before the pointer arrives.
- The sub-menu that closes on the way there. You move diagonally toward an item and pass over a sibling on the way. The sub-menu swaps or shuts before you arrive. The more items in the menu, the harder the diagonal is to complete.
- The tooltip on a timer. It shows for a few seconds, then hides itself. You’re still hovering, and you never dismissed it. Reading speed varies enormously between people, and a timer tuned to how fast the developer reads will always be too short for somebody.
- No way to get rid of it. A large popover covers the content underneath. Escape does nothing, and the only way out is to move the pointer somewhere else entirely. If the thing you need to read is behind the popover, you’re stuck.
Dismissible has two exceptions
You don’t need a dismiss mechanism if the content communicates an input error, or if it doesn’t obscure or replace other content.
So a small tooltip sitting in empty margin space is fine without a dismiss mechanism. The rule applies when the tooltip covers something.
Worth being precise about this one: the exception only lets you off Dismissible. Hoverable and Persistent still apply to that tooltip in the margin.
Solution
Close the gap with a wrapper
Put the hover state on a wrapper that holds both the trigger and the tooltip. Hovering the tooltip keeps the wrapper hovered, so the tooltip stays open.
.tooltip-wrapper:hover .tooltip,
.tooltip-wrapper:focus-within .tooltip {
display: block;
}
:focus-within only fires if the trigger can actually take focus. If your trigger is a styled <span> or a bare icon, give it a real <button> first, or the keyboard half of this never runs.
Close the gap with a transparent border
The wrapper keeps the tooltip open once you’re on it, but it doesn’t help you get there. If a margin separates the two, the pointer still crosses dead space on the way.
A margin between the trigger and the tooltip is dead space. Build the gap from a transparent border instead, and the pointer never loses hover.
.tooltip {
margin-top: 0;
border-top: 1rem solid transparent;
background-clip: padding-box;
}
The border area is transparent but still part of the element, so the pointer stays on the tooltip the whole way across. background-clip: padding-box stops the tooltip’s background painting into that border, so the gap still looks like a gap.
Let the user decide when it goes
Delete the auto-hide timer. The criterion allows exactly three reasons for the content to disappear on its own terms: hover or focus is removed, the user dismisses it, or the information is no longer valid.
A timer is none of those.
Give it an Escape key
One keydown handler covers Dismissible for most tooltips and popovers.
If the content is a popover, you get this for free. A popover with popover=”auto” dismisses on Escape and on a click outside.
Give sub-menus a moment
Don’t close a sub-menu the instant the pointer leaves its parent. A short close delay lets a diagonal path reach its destination.
Larger menus go further and watch the triangle between the pointer and the sub-menu to work out whether someone is still heading there.
What’s excluded
- Browser tooltips from the title attribute. The browser controls those, so the criterion exempts them. That isn’t an endorsement: title is unreliable on keyboard and invisible on touch.
- Content that appears on click. This one is only about hover and focus.
- Content that never appears on keyboard focus at all. That’s a keyboard problem, and 2.1.1 covers it.
How to test
- Hover the trigger, then move the pointer onto the content itself. Does it survive the trip?
- Open it and wait without moving. Does it hide itself while you’re reading?
- Press Escape. Does anything happen?
- Now Tab to the trigger instead of hovering, and run all three again.
Closing
Automated checks can’t do much with this one. It depends on pointer paths and timing, so you find it with your own hands.
Every fix here is small, though: a wrapper, a transparent border, a deleted timer, one key handler.
The content appeared because someone asked for it. Let them decide when it goes.
Learn more
Read the full breakdown: a20y.com/wcag-plain-english/1-4-13-content-on-hover-or-focus
Want more clear and actionable WCAG breakdowns? Check out: wcagInPlainEnglish.com