Plate № 11 · UX primitives · interactivity
A pattern from the gf.cx specimen book
Interactive tooltip · data-tip-href
"Lets A/B test — for selector-area tooltips that have an embedded link that lights up red. This involved smart demarcation of the zone, to allow the user to 'click it' without it disappearing."
The problem
The CSS-only data-tip tooltip is text-only. content: attr(data-tip) renders as a plain string — no HTML, no links, no interactive elements inside the card. The pseudo-element also carries pointer-events: none, so even if you could inject a link, the cursor would fall straight through it. For glossary terms that have canonical reference pages (FCP → web.dev/articles/fcp), a "Learn more ↗" link inside the tooltip is genuinely useful — but CSS can't deliver it.
| Piece | What it does |
|---|---|
data-tip-href="https://…" | Signals that this element needs the JS card — the CSS card is suppressed for these elements via an explicit override rule in tooltip.css |
tooltip-interactive.js | Self-contained IIFE. Queries all [data-tip][data-tip-href] elements, injects a single shared DOM card (.tip-interactive) into document.body, positions it above each trigger on mouseenter |
| 120ms hide delay | The guard that makes the pattern work. On mouseleave a timer schedules hide rather than hiding immediately — the cursor can travel from the trigger across the gap into the card. mouseenter on the card cancels the timer |
| Red "Learn more ↗" link | .tip-interactive__link — color: #c8364c, bold, opens in new tab with rel="noopener" |
The critical trap is the pixel gap between the trigger element and the card (the caret arrow space). If you hide the card on mouseleave of the trigger immediately, the cursor momentarily has no target and the card vanishes before the user reaches the link. The 120ms delay is the fix: small enough to feel instant, large enough for a deliberate move from trigger to card.
The element, in-page
Hover the dotted term below. When the card appears, slide your cursor across the gap to click the red "Learn more" link — the 120ms delay keeps the card open while you travel. The demo renders inline (not in an iframe) so pattern-preview's inline mode can share the tooltip-interactive.js already loaded on this page.
The browser first paints meaningful content at FCP. Both attributes data-tip + data-tip-href are required for the JS card to activate.
The CSS suppression rule (already baked into tooltip.css v0.2.0) prevents the plain CSS card from doubling up with the JS card:
abbr[data-tip][data-tip-href]:hover::after,
abbr[data-tip][data-tip-href]:hover::before { display: none !important; }
Where it applies
- Glossary terms in technical reports that have a canonical reference URL — live in dash.gf.cx/reports/ where terms like FCP, LCP, and CLS carry "Learn more ↗" links to web.dev
- Any tooltip where a secondary action (read more, open docs, copy value) adds genuine utility — the JS card is a general container, not FCP-specific
- Not needed when the tooltip is self-contained — the pure-CSS
data-tippattern is lighter and sufficient for most cases
Reusable elements
The named, copyable pieces — lift any one without the others:
assets.gf.cx/tooltip/tooltip.css— base tooltip styles including the[data-tip][data-tip-href]suppression rule that prevents the CSS pseudo-element card from doubling with the JS card. One<link>tag, no build step.assets.gf.cx/tooltip/tooltip-interactive.js— self-contained IIFE; injects a single shared.tip-interactiveDOM card intodocument.body, positions it above each trigger onmouseenter, tears it down 120ms aftermouseleave. One<script>tag.data-tip="…"attribute contract — the tooltip text label, placed on the trigger element. Required by both the CSS card and as the fallback accessible label (aria-label). The JS card reads this for the card body copy.data-tip-href="https://…"attribute contract — the URL for the "Learn more ↗" link. Presence of this attribute is the signal that activates the JS card; absence leaves the plain CSS tooltip in place. One attribute, no JS class toggling required.- 120ms hide-delay guard — the
setTimeout/clearTimeoutpattern insidetooltip-interactive.js. Copy this single idiom whenever you need a hoverable popup that the cursor must travel into — menus, preview popovers, any case where there is a gap between trigger and floating element.
Reference
- Source
- Dan, 2026-06-01 · interactive tooltip developed during A/B testing on selector-area glossary terms in devreports.
- In use / example
- dash.gf.cx/reports/ — live, with
[data-tip][data-tip-href]on glossary terms (FCP, LCP, CLS, etc.). Open any devreport and hover a dotted term to see the red "Learn more ↗" link in action. - Reusable elements
tooltip.css·tooltip-interactive.js·data-tip/data-tip-hrefattribute contract · 120ms hide-delay guard (listed above).- Origin
- A/B test on selector-area tooltip interactivity, 2026-06-01 — the question was whether a link inside a tooltip is reachable. The 120ms delay is the answer.