Component
Hotspot Tooltip
Onboarding hotspot beacon that opens a multi-step guided tour tooltip. Click the pulsing beacon to start; Next/Done walk the steps, Skip or Escape dismiss it.
- Component
- Tailwind
- HTML
- MIT
Preview
Live component
9:41 100%
devsnips.dev/library/tailwind/components/tooltips/hotspot-tooltip/ fluid · no fixed width 100%
Install
Add to your project
npx devsnips add Tailwind/Components/Tooltips/hotspot-tooltip Tailwind/Components/Tooltips/hotspot-tooltip <!--
Snippet Name: Hotspot Tooltip
Description: Onboarding hotspot beacon with a multi-step tour tooltip. Click to open; Next/Skip walk the steps; Escape or outside click closes.
Author: DevSnips Contributors
Usage Example: Place the data-hotspot-tooltip wrapper next to the feature you announce, then edit the STEPS array in the script.
-->
<div class="relative inline-flex" data-hotspot-tooltip="hotspot-tooltip">
<button
type="button"
class="hs-trigger relative inline-flex h-8 w-8 items-center justify-center rounded-full bg-[#0a0a0a] ring-1 ring-white/10 transition-colors duration-150 hover:bg-[#111111] focus:outline-none focus-visible:ring-2 focus-visible:ring-[#50e3c2] focus-visible:ring-offset-2 motion-reduce:transition-none"
aria-expanded="false"
aria-controls="hotspot-tip"
aria-label="What's new: start the tour"
>
<span aria-hidden="true" class="absolute inset-0 rounded-full bg-[#50e3c2]/40 motion-safe:animate-ping motion-reduce:animate-none"></span>
<span aria-hidden="true" class="relative h-2 w-2 rounded-full bg-[#50e3c2] shadow-[0_0_8px_rgba(80,227,194,0.9)]"></span>
</button>
<div
id="hotspot-tip"
role="dialog"
aria-labelledby="hotspot-tip-title"
class="absolute bottom-full left-1/2 mb-2.5 hidden w-72 max-w-[calc(100vw-2rem)] -translate-x-1/2 opacity-0 scale-95 transition-[opacity,transform] duration-150 ease-out motion-reduce:transition-none"
>
<div class="rounded-lg border border-white/10 bg-[#0a0a0a] shadow-[0_16px_48px_-12px_rgba(0,0,0,0.55)]">
<div class="flex items-start justify-between gap-3 p-4 pb-0">
<span class="hs-eyebrow inline-flex items-center gap-1.5 pt-0.5">
<span aria-hidden="true" class="h-1.5 w-1.5 rounded-full bg-[#50e3c2]"></span>
<span class="text-[11px] font-medium uppercase tracking-widest text-white/40">New feature</span>
</span>
<button
type="button"
class="hs-close -mr-1 -mt-0.5 rounded-md p-1 text-white/40 transition-colors duration-150 hover:bg-white/5 hover:text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-[#50e3c2] motion-reduce:transition-none"
aria-label="Close tour"
>
<svg class="h-3.5 w-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg>
</button>
</div>
<div class="p-4 pb-0">
<h3 id="hotspot-tip-title" class="hs-title text-sm font-semibold tracking-tight text-white"></h3>
<p class="hs-desc mt-1.5 text-[13px] leading-relaxed text-white/60"></p>
<p class="hs-kbd-row mt-3 hidden items-center gap-1 text-[12px] text-white/40">
Try it:
<kbd class="hs-kbd ml-0.5 rounded border border-white/10 bg-white/5 px-1.5 py-0.5 font-sans text-[11px] font-medium text-white/70"></kbd>
</p>
</div>
<div class="flex items-center gap-3 p-4 pt-3">
<div class="hs-dots flex min-w-0 flex-1 items-center gap-1" aria-hidden="true"></div>
<span class="hs-counter text-[11px] tabular-nums text-white/40"></span>
<button
type="button"
class="hs-skip rounded-md px-2 py-1.5 text-xs font-medium text-white/50 transition-colors duration-150 hover:text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-[#50e3c2] motion-reduce:transition-none"
>Skip</button>
<button
type="button"
class="hs-next rounded-md bg-[#50e3c2] px-3 py-1.5 text-xs font-semibold text-[#0a0a0a] transition-colors duration-150 hover:bg-[#7cf0d5] focus:outline-none focus-visible:ring-2 focus-visible:ring-[#50e3c2] focus-visible:ring-offset-2 focus-visible:ring-offset-[#0a0a0a] motion-reduce:transition-none"
>Next</button>
</div>
</div>
<span aria-hidden="true" class="absolute -bottom-1.5 left-1/2 h-3 w-3 -translate-x-1/2 rotate-45 border-b border-r border-white/10 bg-[#0a0a0a]"></span>
</div>
<script>
(function () {
var root = document.currentScript.closest('[data-hotspot-tooltip="hotspot-tooltip"]');
if (!root) return;
var STEPS = [
{
eyebrow: 'New feature',
title: 'Command palette',
desc: 'Jump to any snippet, page, or action without touching the mouse. One keystroke, zero navigation.',
keys: ['\u2318', 'K']
},
{
eyebrow: 'Saved views',
title: 'Pin your go-to snippets',
desc: 'Star the components you reach for daily and they stay pinned in the sidebar, synced across your devices.',
keys: null
},
{
eyebrow: 'Team workspace',
title: 'Share a curated library',
desc: 'Invite your team to one source of truth for snippets, with role-based access and usage insights.',
keys: null
}
];
var trigger = root.querySelector('.hs-trigger');
var tip = root.querySelector('#hotspot-tip');
var eyebrowEl = tip.querySelector('.hs-eyebrow span:last-child');
var titleEl = tip.querySelector('.hs-title');
var descEl = tip.querySelector('.hs-desc');
var kbdRow = tip.querySelector('.hs-kbd-row');
var kbdEl = tip.querySelector('.hs-kbd');
var dotsEl = tip.querySelector('.hs-dots');
var counterEl = tip.querySelector('.hs-counter');
var skipBtn = tip.querySelector('.hs-skip');
var nextBtn = tip.querySelector('.hs-next');
var closeBtn = tip.querySelector('.hs-close');
var current = 0;
var open = false;
var hideTimer = null;
function renderStep() {
var s = STEPS[current];
eyebrowEl.textContent = s.eyebrow;
titleEl.textContent = s.title;
descEl.textContent = s.desc;
if (s.keys) {
kbdEl.textContent = s.keys.join(' ');
kbdRow.classList.remove('hidden');
kbdRow.classList.add('flex');
} else {
kbdRow.classList.add('hidden');
kbdRow.classList.remove('flex');
}
dotsEl.innerHTML = STEPS.map(function (_, i) {
var active = i <= current;
return '<span class="h-1 flex-1 rounded-full ' + (active ? 'bg-[#50e3c2]' : 'bg-white/10') + '"></span>';
}).join('');
counterEl.textContent = (current + 1) + ' / ' + STEPS.length;
nextBtn.textContent = current === STEPS.length - 1 ? 'Done' : 'Next';
}
function setOpen(isOpen) {
open = isOpen;
trigger.setAttribute('aria-expanded', String(isOpen));
if (isOpen) {
renderStep();
if (hideTimer) { clearTimeout(hideTimer); hideTimer = null; }
tip.classList.remove('hidden');
requestAnimationFrame(function () {
tip.classList.remove('opacity-0', 'scale-95');
});
document.addEventListener('pointerdown', onOutside, true);
document.addEventListener('keydown', onKeydown, true);
} else {
tip.classList.add('opacity-0', 'scale-95');
hideTimer = window.setTimeout(function () { tip.classList.add('hidden'); }, 160);
document.removeEventListener('pointerdown', onOutside, true);
document.removeEventListener('keydown', onKeydown, true);
}
}
function onOutside(e) {
if (!root.contains(e.target)) setOpen(false);
}
function onKeydown(e) {
if (e.key === 'Escape') {
setOpen(false);
trigger.focus();
}
}
trigger.addEventListener('click', function () { setOpen(!open); });
closeBtn.addEventListener('click', function () { setOpen(false); });
skipBtn.addEventListener('click', function () { current = 0; setOpen(false); });
nextBtn.addEventListener('click', function () {
if (current < STEPS.length - 1) {
current += 1;
renderStep();
} else {
current = 0;
setOpen(false);
}
});
})();
</script>
</div> # Hotspot Tooltip
Hotspot-triggered tooltip.
## Usage
```bash
npx devsnips add Tailwind/Components/Tooltips/hotspot-tooltip
```
Copy from `code.html`.
## File Structure
`code.html` · `preview.html` · `metadata.json` · `README.md` 174 lines UTF-8 · LF · Spaces: 2
Continue browsing