Inline Tooltip
betaA docs-style tooltip attached to a term in flowing text — dotted underline, bubble on hover, focus or tap.
Default
Hover or focus a dotted term.
Sides
The bubble opens above the term, or below it on request.
Rich content
Any phrasing content works, including a link you can Tab into.
Notes
A term with a dotted underline and a popover-styled bubble, built for running prose rather than for buttons and icons.
- Accessibility contract:
aria-describedbyis wired unconditionally, so the bubble's text is part of the term's accessible description whether or not it is visible. Hover and keyboard focus both open it, Escape closes it and returns focus to the term, and tabbing away closes it. - Focus handling is focus-within the wrapper, so a keyboard user can Tab
from the term into a link inside the bubble. While closed the bubble is
invisibleandpointer-events-none, andvisibility: hiddencontent is neither focusable nor clickable — closed tooltips add no tab stops. - Strictly speaking,
role="tooltip"content shouldn't be interactive; a tooltip with a link in it edges toward a toggletip. The role stays because the always-wired description means the link's text is announced either way, and the keyboard path is real. - Touch: pointer events (not mouse events) let the synthetic hover be
ignored by
pointerType, so a tap toggles instead of open-then-closing. A tap or click anywhere outside dismisses. - 100ms open / 150ms close delays keep passing pointers from flickering the bubble, and the close delay is what lets the pointer cross the gap into the bubble to select text or click a link.
- Animation is
motion-safe:only — reduced-motion users get instant show and hide.visibilityis transitioned alongside opacity so the fade-out isn't cut short. - No portal: the bubble is absolutely positioned inside a relative
wrapper, which is what keeps it inside a scoped
.dark/.lightsubtree (a portal to<body>would render in the page's theme). The cost is no collision detection — picksideyourself — and clipping inside anoverflow-hiddenancestor. - The wrapper is
inline-block, so a term can't break across lines. Keep triggers to a word or short phrase. contentmust be phrasing content — the component is all<span>s so it can live inside a<p>, and a<div>in the content would break that nesting.