LeapUI

Inline Tooltip

beta

A docs-style tooltip attached to a term in flowing text — dotted underline, bubble on hover, focus or tap.

tooltiphovertextAdded 2026-08-17

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-describedby is 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 invisible and pointer-events-none, and visibility: hidden content 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. visibility is 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/.light subtree (a portal to <body> would render in the page's theme). The cost is no collision detection — pick side yourself — and clipping inside an overflow-hidden ancestor.
  • The wrapper is inline-block, so a term can't break across lines. Keep triggers to a word or short phrase.
  • content must 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.