Hand-rolling a tooltip in plain HTML with anchor-positioning
A small tooltip pattern using semantic markup, CSS anchor positioning, keyboard behavior, and a practical fallback.
Tooltips look simple until they have to behave. They need to point at the right thing, avoid the viewport edge, appear for keyboard users, disappear predictably, and not become the only place important information exists.
I still like hand-rolling small tooltips because it forces the right questions early. Is this truly a tooltip, or is it a popover with interactive content? Does the trigger have an accessible name? Can the user complete the task without reading the tooltip? What happens on touch?
For this pattern, assume a noninteractive tooltip: short explanatory text attached to a button. If the content has links, inputs, or actions, I would move to a popover or dialog pattern instead.
Start with semantic trigger and content
The trigger should be a real button when it is focusable and interactive. The tooltip content should be referenced with aria-describedby so assistive technology can associate the explanation with the trigger.
<button class="info-button" aria-describedby="shipping-tip" type="button">
Shipping estimate
</button>
<span class="tooltip" id="shipping-tip" role="tooltip">
Final delivery dates depend on carrier pickup after payment.
</span>
Do not put essential policy only in the tooltip. If the information changes the user's decision, it probably belongs inline. Tooltips are best for clarification, not disclosure.
Anchor the tooltip in CSS
CSS anchor positioning lets the tooltip position itself relative to the trigger without measuring coordinates in JavaScript. The trigger defines an anchor name. The tooltip chooses that anchor and places itself against it.
.info-button {
anchor-name: --shipping-tip;
}
.tooltip {
position: absolute;
position-anchor: --shipping-tip;
inset-block-end: anchor(top);
justify-self: anchor-center;
margin-block-end: 8px;
max-inline-size: 240px;
padding: 8px 10px;
border-radius: 6px;
background: oklch(0.2 0.02 250);
color: white;
font: 500 0.8125rem/1.35 system-ui;
opacity: 0;
transform: translateY(2px);
transition: opacity 140ms ease, transform 140ms ease;
pointer-events: none;
}
.info-button:hover + .tooltip,
.info-button:focus-visible + .tooltip {
opacity: 1;
transform: translateY(0);
}
The important part is that positioning belongs to CSS. JavaScript should not be doing layout math for this simple case.
Add overflow fallback positions
A tooltip above the trigger is fine until the trigger sits near the top edge. Anchor positioning includes fallback placement through position-try-fallbacks.
.tooltip {
position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline;
}
That tells the browser to try alternate placements when the preferred position would overflow. I still test narrow screens manually because fallback behavior can reveal copy problems. A tooltip with too much text is not fixed by smarter positioning.
Keyboard and focus behavior
Hover-only tooltips fail keyboard users. At minimum, show the tooltip on focus-visible and hide it on blur. For a purely CSS tooltip, the sibling selector above handles that.
Escape is where plain CSS stops being enough. If the tooltip is short, noninteractive, and tied to focus, blur may be acceptable. If the tooltip can stay open after a click, add a tiny script that closes it on Escape and returns to a consistent state.
Touch is another pressure test. On touch devices, hover does not exist in a reliable way. I usually either show the clarification inline on small screens or use a disclosure button that toggles a popover. A tooltip that can only be discovered by accidental long press is not a real interface.
Use progressive enhancement
Anchor positioning is the enhancement. The fallback can be a conservative absolute position near the trigger or an inline help text pattern on small screens.
@supports not (position-anchor: --shipping-tip) {
.tooltip {
position: static;
display: block;
margin-block-start: 6px;
opacity: 1;
transform: none;
color: oklch(0.42 0.03 250);
background: transparent;
padding: 0;
}
}
This fallback is intentionally plain. It chooses readability over imitation. If the browser cannot place the tooltip reliably, the user still gets the content.
My rule for tooltips
A tooltip should make a good interface slightly clearer. It should not rescue a confusing control, hide required information, or become a tiny modal without focus management.
When the content is short, noninteractive, and genuinely supplemental, HTML plus CSS anchor positioning can be enough. When the content becomes important or interactive, promote it to a pattern that deserves stronger semantics and behavior.