Tooltip Patterns
A tooltip is a small piece of supplementary text that appears near an element on hover or focus — a term definition, an abbreviation expansion, a hint about what an icon button does. The interesting CSS problems are positioning it without clipping, delaying its appearance so it does not flash on every incidental mouse pass, and making sure keyboard and screen reader users get the same information mouse users do.
Pure CSS tooltip with pseudo-elements and a data attribute
The classic no-JavaScript tooltip: store the tooltip text in a data attribute, then pull it into a generated ::before/::after box shown only on hover/focus.
<button class="has-tooltip" data-tooltip="Copy to clipboard" aria-describedby="tt-copy"> Copy </button>
.has-tooltip {
position: relative;
}
.has-tooltip::after {
content: attr(data-tooltip);
position: absolute;
bottom: calc(100% + 6px);
left: 50%;
transform: translateX(-50%);
padding: 4px 8px;
border-radius: 4px;
background: #1e293b;
color: white;
font-size: 0.8rem;
white-space: nowrap;
opacity: 0;
pointer-events: none;
transition: opacity 0.15s ease;
}
.has-tooltip:hover::after,
.has-tooltip:focus-visible::after {
opacity: 1;
}content: attr(data-tooltip)pulls the label straight from the markup — one source of truth, no duplicated text in the CSS.pointer-events: noneon the tooltip box keeps it from intercepting hover/click events meant for whatever is underneath it once it fades.Triggering on both
:hoverand:focus-visible(not just:hover) is what makes this keyboard-accessible at all — see the accessibility section below.
Positioning quirks
A tooltip positioned with plain absolute positioning relative to its trigger can silently clip against a scrolling or overflow: hidden ancestor, or run off the edge of the viewport near screen edges — the two most common tooltip bugs in production.
Problem | Cause | Fix |
|---|---|---|
Tooltip gets clipped/cut off | An ancestor has | Portal the tooltip to |
Tooltip runs off-screen near an edge | Fixed placement (always "above, centered") does not adapt to available space | Use |
Tooltip appears behind other content | A low or default | Raise |
The modern fix — CSS Anchor Positioning
The anchor positioning API lets an element position itself relative to another element anywhere in the DOM — not just an ancestor — solving the overflow-clipping problem at the source.
.trigger {
anchor-name: --copy-btn;
}
.tooltip {
position: fixed;
position-anchor: --copy-btn;
bottom: anchor(top);
left: anchor(center);
translate: -50% -6px;
}@position-try fallback positions that automatically flip the tooltip when it would otherwise overflow the viewport — the declarative replacement for hand-rolled JS collision detection.The popover attribute approach
For a tooltip that should render above absolutely everything (including inside a scroll container or a modal), the popover attribute (see Modal & Dialog Patterns) renders in the top layer, sidestepping z-index and overflow clipping entirely.
<button popovertarget="tt-copy" popovertargetaction="show">Copy</button> <div id="tt-copy" popover="hint" role="tooltip">Copy to clipboard</div>
popover="hint" is a lighter-weight popover mode built specifically for this case: unlike popover="auto", a hint popover does not close other open auto popovers when it opens, which matters since a tooltip should be able to appear over an open menu without dismissing it.Delays with transition
A tooltip that appears the instant the cursor grazes an element is noisy. Delay the entrance (not the exit) with transition-delay.
.has-tooltip::after {
opacity: 0;
transition: opacity 0.15s ease 0s; /* no delay hiding */
}
.has-tooltip:hover::after {
transition-delay: 0.4s; /* wait before showing */
}Accessibility — aria-describedby, hover, and focus
A tooltip must be reachable by keyboard, not just mouse — trigger it on
:focus-visiblein addition to:hover, never:hoveralone.Associate the tooltip text with its trigger via
aria-describedby="id"pointing at the tooltip element'sid, so screen readers announce it as supplementary description, not just visually-adjacent text.Give the tooltip element
role="tooltip"so assistive tech understands its purpose, especially when using the popover or anchor-positioning approaches rather than a pure::after(which has no separate DOM node to attach a role to).Never put essential, non-duplicated information only inside a tooltip — touch-only users have no hover state at all, so critical info needs a fallback (a visible label, an always-present icon caption).
title attribute tooltips for anything important — they are inconsistent across browsers, invisible on touch devices, and cannot be styled at all. Treat title as a last-resort fallback, never the primary implementation.Related pages: Anchor Positioning, Modal & Dialog Patterns, :focus-visible & :focus-within, and transition-property.