Open demo

CSS code

HTML
<svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false">
  <symbol id="i-heart" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linejoin="round"><path d="M12 20s-7.5-4.6-7.5-10.2A4.3 4.3 0 0 1 12 7.3a4.3 4.3 0 0 1 7.5 2.5C19.5 15.4 12 20 12 20z"/></symbol>
  <symbol id="i-repeat" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M17 3.5 20 6.5l-3 3"/><path d="M4 11.5v-1a4 4 0 0 1 4-4h12M7 20.5l-3-3 3-3"/><path d="M20 12.5v1a4 4 0 0 1-4 4H4"/></symbol>
  <symbol id="i-bookmark" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linejoin="round"><path d="M6.5 3.5h11v17L12 16.5l-5.5 4z"/></symbol>
  <symbol id="i-bell" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M6 16.5V11a6 6 0 0 1 12 0v5.5l1.5 2h-15z"/><path d="M10 20.5a2 2 0 0 0 4 0"/></symbol>
  <symbol id="i-send" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M20.5 3.5 10 14M20.5 3.5l-6.5 17-4-6.5-6.5-4z"/></symbol>
</svg>

<div><span class="lbl">Five entrances</span>
<div class="stage cells">
  <div class="cell"><span class="tt"><button class="btn" type="button" aria-describedby="fx-1"><svg aria-hidden="true"><use href="#i-heart"/></svg>Like</button><span class="tt-tip" role="tooltip" id="fx-1">Priya and 41 others</span></span><small>Fade</small></div>
  <div class="cell"><span class="tt fx-scale"><button class="btn" type="button" aria-describedby="fx-2"><svg aria-hidden="true"><use href="#i-repeat"/></svg>Repost</button><span class="tt-tip" role="tooltip" id="fx-2">Share with your 1,204 followers</span></span><small>Scale from the arrow</small></div>
  <div class="cell"><span class="tt fx-slide"><button class="btn" type="button" aria-describedby="fx-3"><svg aria-hidden="true"><use href="#i-bookmark"/></svg>Save</button><span class="tt-tip" role="tooltip" id="fx-3">Only you can see saved posts</span></span><small>Slide up</small></div>
  <div class="cell"><span class="tt fx-spring"><button class="btn" type="button" aria-describedby="fx-4"><svg aria-hidden="true"><use href="#i-bell"/></svg>Follow</button><span class="tt-tip" role="tooltip" id="fx-4">Get a ping when Noor posts</span></span><small>Spring</small></div>
  <div class="cell"><span class="tt fx-blur"><button class="btn" type="button" aria-describedby="fx-5"><svg aria-hidden="true"><use href="#i-send"/></svg>Send</button><span class="tt-tip" role="tooltip" id="fx-5">Send in a direct message</span></span><small>Blur in</small></div>
</div></div>

<div><span class="lbl">Delay on hover, instant on focus</span>
<div class="stage">
  <div class="swatches" role="group" aria-label="Glaze colours">
    <span class="tt fx-delay fx-slide"><button class="sw" type="button" style="--c:#c2410c" aria-label="Terracotta" aria-describedby="sw-1"></button><span class="tt-tip" role="tooltip" id="sw-1">Terracotta · in stock</span></span>
    <span class="tt fx-delay fx-slide"><button class="sw" type="button" style="--c:#b45309" aria-label="Ochre" aria-describedby="sw-2"></button><span class="tt-tip" role="tooltip" id="sw-2">Ochre · in stock</span></span>
    <span class="tt fx-delay fx-slide"><button class="sw" type="button" style="--c:#4d7c0f" aria-label="Moss" aria-describedby="sw-3"></button><span class="tt-tip" role="tooltip" id="sw-3">Moss · 3 left</span></span>
    <span class="tt fx-delay fx-slide"><button class="sw" type="button" style="--c:#0f766e" aria-label="Lagoon" aria-describedby="sw-4"></button><span class="tt-tip" role="tooltip" id="sw-4">Lagoon · back in May</span></span>
    <span class="tt fx-delay fx-slide"><button class="sw" type="button" style="--c:#6d28d9" aria-label="Iris" aria-describedby="sw-5"></button><span class="tt-tip" role="tooltip" id="sw-5">Iris · in stock</span></span>
    <span class="tt fx-delay fx-slide"><button class="sw" type="button" style="--c:#1e293b" aria-label="Slate" aria-describedby="sw-6"></button><span class="tt-tip" role="tooltip" id="sw-6">Slate · in stock</span></span>
  </div>
</div></div>
CSS
/* Animated tooltips: each fx-* class sets the hidden state; the open state always ends at no transform.
   Under prefers-reduced-motion only a short fade remains. */
.tt{--tt-bg:#3b0d1f;--tt-ink:#fff5f8;--tt-gap:10px;--tt-arrow:6px;--tt-in:.2s;position:relative;display:inline-flex;vertical-align:middle}
.tt-tip{position:absolute;z-index:30;bottom:calc(100% + var(--tt-gap));left:50%;translate:calc(-50% + var(--tt-nudge,0px)) 0;width:max-content;max-width:200px;padding:7px 11px;border-radius:9px;
  background:var(--tt-bg);color:var(--tt-ink);font:500 .8125rem/1.45 ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;text-align:center;
  box-shadow:0 12px 26px -12px rgba(59,13,31,.6);transform-origin:50% calc(100% + var(--tt-arrow));
  visibility:hidden;opacity:0;scale:0;transition:opacity var(--tt-in),visibility var(--tt-in),transform var(--tt-in),filter var(--tt-in),scale 0s var(--tt-in)}
.tt:is(:hover,:focus-within,.is-open):not(.is-dismissed)>.tt-tip{visibility:visible;opacity:1;scale:1;transform:none;filter:none;
  transition:opacity var(--tt-in),visibility 0s,transform var(--tt-in) var(--tt-ease,ease-out),filter var(--tt-in),scale 0s}
.tt-tip::before{content:"";position:absolute;top:100%;left:calc(50% - var(--tt-arrow) - var(--tt-nudge,0px));border:var(--tt-arrow) solid transparent;border-bottom-width:0;border-top-color:var(--tt-bg)}
.tt-tip::after{content:"";position:absolute;top:100%;left:0;right:0;height:var(--tt-gap)}

.fx-scale>.tt-tip{transform:scale(.8)}
.fx-slide>.tt-tip{transform:translateY(8px)}
.fx-spring{--tt-in:.5s;--tt-ease:linear(0,.25 8%,.82 24%,1.08 36%,1.1 42%,1.03 56%,.99 68%,1)}
.fx-spring>.tt-tip{transform:scale(.5)}
.fx-blur>.tt-tip{transform:scale(1.06);filter:blur(6px)}
/* Delayed: waits 400 ms on hover so sweeping the pointer across a row does not flash tooltips; keyboard focus is instant. */
.fx-delay:hover:not(:focus-within,.is-open,.is-dismissed)>.tt-tip{transition-delay:.4s}

/* Triggers */
.btn{display:inline-flex;align-items:center;gap:8px;height:38px;padding:0 14px;border:1px solid #ecc9d4;border-radius:999px;background:#fff;color:#2a1520;
  font:600 .875rem/1 ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;cursor:pointer;transition:background-color .15s}
.btn:hover{background:#fdf2f4}
.btn svg{width:17px;height:17px;color:#be123c}
.swatches{display:flex;flex-wrap:wrap;gap:12px;padding-top:56px}
.sw{width:44px;height:44px;padding:0;border:3px solid #fff;border-radius:50%;background:var(--c);box-shadow:0 0 0 1px #ecc9d4;cursor:pointer}
:is(.btn,.sw):focus-visible{outline:2px solid #be123c;outline-offset:3px}
@media (prefers-reduced-motion:reduce){
  .tt .tt-tip,.tt:is(:hover,:focus-within,.is-open):not(.is-dismissed)>.tt-tip{transform:none;filter:none;transition:opacity .1s,visibility .1s}
  .btn{transition:none}
}
JavaScript
// Tooltip behaviour for WCAG 1.4.13: Esc dismisses without moving focus or the pointer, a tap opens it on touch screens,
// opening one closes the others, and tooltips are nudged sideways to stay inside the window.
(() => {
  const tts = [...document.querySelectorAll('.tt')];
  const fit = t => {
    const tip = t.querySelector(':scope > [role="tooltip"]'); if (!tip) return;
    tip.style.removeProperty('--tt-nudge');
    requestAnimationFrame(() => {
      const r = tip.getBoundingClientRect(), w = document.documentElement.clientWidth;
      const d = r.left < 8 ? 8 - r.left : r.right > w - 8 ? w - 8 - r.right : 0;
      if (d) tip.style.setProperty('--tt-nudge', d + 'px');
    });
  };
  const enter = t => { tts.forEach(o => o !== t && o.classList.remove('is-open')); t.classList.remove('is-dismissed'); fit(t); };
  tts.forEach(t => { t.addEventListener('pointerenter', () => enter(t)); t.addEventListener('focusin', () => enter(t)); if (t.classList.contains('is-open')) fit(t); });
  document.addEventListener('pointerdown', e => {
    const t = e.target.closest('.tt');
    if (!t) tts.forEach(o => o.classList.remove('is-open'));
    else if (e.pointerType === 'touch') t.classList.add('is-open');
  });
  document.addEventListener('keydown', e => {
    if (e.key !== 'Escape') return;
    tts.filter(t => t.matches(':hover, :focus-within, .is-open')).forEach(t => { t.classList.remove('is-open'); t.classList.add('is-dismissed'); });
  });
})();

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS, Bootstrap 5.3.8 JS bundle

HTML
<svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false">
  <symbol id="i-heart" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linejoin="round"><path d="M12 20s-7.5-4.6-7.5-10.2A4.3 4.3 0 0 1 12 7.3a4.3 4.3 0 0 1 7.5 2.5C19.5 15.4 12 20 12 20z"/></symbol>
  <symbol id="i-repeat" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M17 3.5 20 6.5l-3 3"/><path d="M4 11.5v-1a4 4 0 0 1 4-4h12M7 20.5l-3-3 3-3"/><path d="M20 12.5v1a4 4 0 0 1-4 4H4"/></symbol>
  <symbol id="i-bookmark" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linejoin="round"><path d="M6.5 3.5h11v17L12 16.5l-5.5 4z"/></symbol>
  <symbol id="i-bell" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M6 16.5V11a6 6 0 0 1 12 0v5.5l1.5 2h-15z"/><path d="M10 20.5a2 2 0 0 0 4 0"/></symbol>
  <symbol id="i-send" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M20.5 3.5 10 14M20.5 3.5l-6.5 17-4-6.5-6.5-4z"/></symbol>
</svg>

<div><span class="lbl">Five entrances</span>
<div class="stage cells">
  <div class="cell"><button class="btn btn-rose" type="button" data-bs-toggle="tooltip" data-bs-title="Priya and 41 others"><svg aria-hidden="true"><use href="#i-heart"/></svg>Like</button><small>Fade</small></div>
  <div class="cell"><button class="btn btn-rose" type="button" data-bs-toggle="tooltip" data-bs-custom-class="fx-scale" data-bs-title="Share with your 1,204 followers"><svg aria-hidden="true"><use href="#i-repeat"/></svg>Repost</button><small>Scale from the arrow</small></div>
  <div class="cell"><button class="btn btn-rose" type="button" data-bs-toggle="tooltip" data-bs-custom-class="fx-slide" data-bs-title="Only you can see saved posts"><svg aria-hidden="true"><use href="#i-bookmark"/></svg>Save</button><small>Slide up</small></div>
  <div class="cell"><button class="btn btn-rose" type="button" data-bs-toggle="tooltip" data-bs-custom-class="fx-spring" data-bs-title="Get a ping when Noor posts"><svg aria-hidden="true"><use href="#i-bell"/></svg>Follow</button><small>Spring</small></div>
  <div class="cell"><button class="btn btn-rose" type="button" data-bs-toggle="tooltip" data-bs-custom-class="fx-blur" data-bs-title="Send in a direct message"><svg aria-hidden="true"><use href="#i-send"/></svg>Send</button><small>Blur in</small></div>
</div></div>

<div><span class="lbl">Delay on hover, instant on focus</span>
<div class="stage">
  <div class="swatches d-flex flex-wrap gap-3" role="group" aria-label="Glaze colours">
    <button class="sw rounded-circle" type="button" style="--c:#c2410c" aria-label="Terracotta" data-bs-toggle="tooltip" data-bs-custom-class="fx-slide" data-tt-delay="400" data-bs-title="Terracotta · in stock"></button>
    <button class="sw rounded-circle" type="button" style="--c:#b45309" aria-label="Ochre" data-bs-toggle="tooltip" data-bs-custom-class="fx-slide" data-tt-delay="400" data-bs-title="Ochre · in stock"></button>
    <button class="sw rounded-circle" type="button" id="sw-moss" style="--c:#4d7c0f" aria-label="Moss" data-bs-toggle="tooltip" data-bs-custom-class="fx-slide" data-tt-delay="400" data-bs-title="Moss · 3 left"></button>
    <button class="sw rounded-circle" type="button" style="--c:#0f766e" aria-label="Lagoon" data-bs-toggle="tooltip" data-bs-custom-class="fx-slide" data-tt-delay="400" data-bs-title="Lagoon · back in May"></button>
    <button class="sw rounded-circle" type="button" style="--c:#6d28d9" aria-label="Iris" data-bs-toggle="tooltip" data-bs-custom-class="fx-slide" data-tt-delay="400" data-bs-title="Iris · in stock"></button>
    <button class="sw rounded-circle" type="button" style="--c:#1e293b" aria-label="Slate" data-bs-toggle="tooltip" data-bs-custom-class="fx-slide" data-tt-delay="400" data-bs-title="Slate · in stock"></button>
  </div>
</div></div>
CSS
/* Bootstrap Tooltip with entrance animations. Popper positions .tooltip with transform, so the keyframes use the separate
   scale and translate properties, which add to it. Each fx-* custom class picks an entrance; reduced motion keeps a short fade. */
.tooltip.tt{--bs-tooltip-bg:#3b0d1f;--bs-tooltip-color:#fff5f8;--bs-tooltip-opacity:1;--bs-tooltip-max-width:200px;--bs-tooltip-padding-x:11px;--bs-tooltip-padding-y:7px;
  --bs-tooltip-font-size:.8125rem;--bs-tooltip-border-radius:9px;--bs-tooltip-arrow-width:12px;--bs-tooltip-arrow-height:6px;font-family:inherit;transform-origin:50% 100%}
.tooltip.tt .tooltip-inner{font-weight:500;line-height:1.45;box-shadow:0 12px 26px -12px rgba(59,13,31,.6)}
.tooltip.tt.show{animation:fx-fade .2s ease-out}
.tooltip.fx-scale.show{animation-name:fx-scale}
.tooltip.fx-slide.show{animation-name:fx-slide}
.tooltip.fx-spring.show{animation:fx-spring .5s linear(0,.25 8%,.82 24%,1.08 36%,1.1 42%,1.03 56%,.99 68%,1)}
.tooltip.fx-blur.show{animation-name:fx-blur}
@keyframes fx-fade{from{opacity:0}}
@keyframes fx-scale{from{opacity:0;scale:.8}}
@keyframes fx-slide{from{opacity:0;translate:0 8px}}
@keyframes fx-spring{from{opacity:0;scale:.5}}
@keyframes fx-blur{from{opacity:0;scale:1.06;filter:blur(6px)}}

.btn-rose{--bs-btn-padding-x:14px;--bs-btn-font-size:.875rem;--bs-btn-font-weight:600;--bs-btn-line-height:1;--bs-btn-border-radius:999px;--bs-btn-color:#2a1520;--bs-btn-bg:#fff;
  --bs-btn-border-color:#ecc9d4;--bs-btn-hover-color:#2a1520;--bs-btn-hover-bg:#fdf2f4;--bs-btn-hover-border-color:#ecc9d4;--bs-btn-active-color:#2a1520;--bs-btn-active-bg:#fbe4ea;
  --bs-btn-active-border-color:#ecc9d4;display:inline-flex;align-items:center;gap:8px;height:38px}
.btn-rose svg{width:17px;height:17px;color:#be123c}
.swatches{padding-top:56px}
.sw{width:44px;height:44px;padding:0;border:3px solid #fff;background:var(--c);box-shadow:0 0 0 1px #ecc9d4}
:is(.btn-rose,.sw):focus-visible{outline:2px solid #be123c;outline-offset:3px;box-shadow:none}
.sw:focus-visible{box-shadow:0 0 0 1px #ecc9d4}
@media (prefers-reduced-motion:reduce){.tooltip.tt.show{animation:fx-fade .1s linear}.btn{transition:none}}
JavaScript
// Bootstrap Tooltip (its own fade is off; the CSS keyframes animate the entrance), triggered by this script so it meets
// WCAG 1.4.13: hover, keyboard focus and tap open it, it stays open under the pointer, Esc closes it, one is open at a time.
// data-tt-delay: wait that many milliseconds before opening on hover; keyboard focus still opens it at once.
(() => {
  const tips = new Map();
  const hideAll = keep => tips.forEach(t => t !== keep && t.hide());
  document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(el => {
    const tip = new bootstrap.Tooltip(el, { trigger: 'manual', animation: false, placement: 'top', offset: [0, 10],
      customClass: ('tt ' + (el.dataset.bsCustomClass || '')).trim() });
    tips.set(el, tip);
    let open = false, timer;
    el.addEventListener('show.bs.tooltip', () => { open = true; });
    el.addEventListener('hide.bs.tooltip', () => { open = false; });
    el.addEventListener('inserted.bs.tooltip', () => {
      tip.tip.addEventListener('mouseenter', () => clearTimeout(timer));
      tip.tip.addEventListener('mouseleave', leave);
    });
    const show = () => { clearTimeout(timer); hideAll(tip); if (!open) tip.show(); };
    const leave = () => { clearTimeout(timer); timer = setTimeout(() => { if (el !== document.activeElement) tip.hide(); }, 150); };
    el.addEventListener('mouseenter', () => { clearTimeout(timer); if (open) return; timer = setTimeout(show, +el.dataset.ttDelay || 0); });
    el.addEventListener('focus', () => { clearTimeout(timer); if (!open) tip.show(); });
    el.addEventListener('mouseleave', leave);
    el.addEventListener('blur', () => { if (!el.matches(':hover')) tip.hide(); });
  });
  document.addEventListener('pointerdown', e => { if (![...tips.keys()].some(el => el.contains(e.target)) && !e.target.closest('.tooltip')) hideAll(); });
  document.addEventListener('keydown', e => { if (e.key === 'Escape') hideAll(); });
})();

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false">
  <symbol id="i-heart" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linejoin="round"><path d="M12 20s-7.5-4.6-7.5-10.2A4.3 4.3 0 0 1 12 7.3a4.3 4.3 0 0 1 7.5 2.5C19.5 15.4 12 20 12 20z"/></symbol>
  <symbol id="i-repeat" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M17 3.5 20 6.5l-3 3"/><path d="M4 11.5v-1a4 4 0 0 1 4-4h12M7 20.5l-3-3 3-3"/><path d="M20 12.5v1a4 4 0 0 1-4 4H4"/></symbol>
  <symbol id="i-bookmark" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linejoin="round"><path d="M6.5 3.5h11v17L12 16.5l-5.5 4z"/></symbol>
  <symbol id="i-bell" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M6 16.5V11a6 6 0 0 1 12 0v5.5l1.5 2h-15z"/><path d="M10 20.5a2 2 0 0 0 4 0"/></symbol>
  <symbol id="i-send" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M20.5 3.5 10 14M20.5 3.5l-6.5 17-4-6.5-6.5-4z"/></symbol>
</svg>

<div><span class="mb-[14px] block text-[.72rem] tracking-[.12em] text-[#6b4a57] uppercase">Five entrances</span>
<div class="cells rounded-[18px] border border-[#f3dce3] bg-white p-7 max-[600px]:p-5 grid grid-cols-[repeat(auto-fit,minmax(240px,1fr))] gap-3">
  <div class="flex min-h-[150px] flex-col items-center justify-end gap-2.5 rounded-xl border border-dashed border-[#ecc9d4] bg-[#fffafb] p-3.5"><span class="tt relative inline-flex align-middle [--tt-in:.2s]"><button class="inline-flex h-[38px] cursor-pointer items-center gap-2 rounded-full border border-[#ecc9d4] bg-white px-3.5 text-sm leading-none font-semibold text-[#2a1520] transition-colors duration-150 hover:bg-[#fdf2f4] motion-reduce:transition-none focus-visible:outline-2 focus-visible:outline-offset-[3px] focus-visible:outline-[#be123c]" type="button" aria-describedby="fx-1"><svg class="size-[17px] text-[#be123c]" aria-hidden="true"><use href="#i-heart"/></svg>Like</button><span class="pointer-events-auto absolute z-30 w-max max-w-[200px] rounded-[9px] bg-tt px-[11px] py-[7px] text-[.8125rem] leading-[1.45] font-medium text-tt-ink whitespace-normal shadow-[0_12px_26px_-12px_rgba(59,13,31,.6)] before:absolute before:border-transparent after:absolute before:border-[6px] invisible opacity-0 scale-0 origin-[50%_calc(100%+6px)] tt-open:visible tt-open:opacity-100 tt-open:scale-100 tt-open:[transform:none] tt-open:[filter:none] motion-safe:[transition:opacity_var(--tt-in),visibility_var(--tt-in),transform_var(--tt-in),filter_var(--tt-in),scale_0s_var(--tt-in)] motion-safe:tt-open:[transition:opacity_var(--tt-in),visibility_0s,transform_var(--tt-in)_var(--tt-ease,ease-out),filter_var(--tt-in),scale_0s] motion-reduce:[transition:opacity_.1s,visibility_.1s] motion-reduce:tt-open:[transition:opacity_.1s,visibility_.1s] text-center tt-top bottom-[calc(100%+10px)] left-1/2 translate-x-[calc(-50%+var(--tt-nudge,0px))] before:top-full before:left-[calc(50%-6px-var(--tt-nudge,0px))] before:border-b-0 before:border-t-tt after:inset-x-0 after:top-full after:h-[10px]" role="tooltip" id="fx-1">Priya and 41 others</span></span><small class="text-xs font-semibold text-[#6b4a57]">Fade</small></div>
  <div class="flex min-h-[150px] flex-col items-center justify-end gap-2.5 rounded-xl border border-dashed border-[#ecc9d4] bg-[#fffafb] p-3.5"><span class="tt relative inline-flex align-middle [--tt-in:.2s]"><button class="inline-flex h-[38px] cursor-pointer items-center gap-2 rounded-full border border-[#ecc9d4] bg-white px-3.5 text-sm leading-none font-semibold text-[#2a1520] transition-colors duration-150 hover:bg-[#fdf2f4] motion-reduce:transition-none focus-visible:outline-2 focus-visible:outline-offset-[3px] focus-visible:outline-[#be123c]" type="button" aria-describedby="fx-2"><svg class="size-[17px] text-[#be123c]" aria-hidden="true"><use href="#i-repeat"/></svg>Repost</button><span class="pointer-events-auto absolute z-30 w-max max-w-[200px] rounded-[9px] bg-tt px-[11px] py-[7px] text-[.8125rem] leading-[1.45] font-medium text-tt-ink whitespace-normal shadow-[0_12px_26px_-12px_rgba(59,13,31,.6)] before:absolute before:border-transparent after:absolute before:border-[6px] invisible opacity-0 scale-0 origin-[50%_calc(100%+6px)] tt-open:visible tt-open:opacity-100 tt-open:scale-100 tt-open:[transform:none] tt-open:[filter:none] motion-safe:[transition:opacity_var(--tt-in),visibility_var(--tt-in),transform_var(--tt-in),filter_var(--tt-in),scale_0s_var(--tt-in)] motion-safe:tt-open:[transition:opacity_var(--tt-in),visibility_0s,transform_var(--tt-in)_var(--tt-ease,ease-out),filter_var(--tt-in),scale_0s] motion-reduce:[transition:opacity_.1s,visibility_.1s] motion-reduce:tt-open:[transition:opacity_.1s,visibility_.1s] text-center tt-top bottom-[calc(100%+10px)] left-1/2 translate-x-[calc(-50%+var(--tt-nudge,0px))] before:top-full before:left-[calc(50%-6px-var(--tt-nudge,0px))] before:border-b-0 before:border-t-tt after:inset-x-0 after:top-full after:h-[10px] motion-safe:[transform:scale(.8)]" role="tooltip" id="fx-2">Share with your 1,204 followers</span></span><small class="text-xs font-semibold text-[#6b4a57]">Scale from the arrow</small></div>
  <div class="flex min-h-[150px] flex-col items-center justify-end gap-2.5 rounded-xl border border-dashed border-[#ecc9d4] bg-[#fffafb] p-3.5"><span class="tt relative inline-flex align-middle [--tt-in:.2s]"><button class="inline-flex h-[38px] cursor-pointer items-center gap-2 rounded-full border border-[#ecc9d4] bg-white px-3.5 text-sm leading-none font-semibold text-[#2a1520] transition-colors duration-150 hover:bg-[#fdf2f4] motion-reduce:transition-none focus-visible:outline-2 focus-visible:outline-offset-[3px] focus-visible:outline-[#be123c]" type="button" aria-describedby="fx-3"><svg class="size-[17px] text-[#be123c]" aria-hidden="true"><use href="#i-bookmark"/></svg>Save</button><span class="pointer-events-auto absolute z-30 w-max max-w-[200px] rounded-[9px] bg-tt px-[11px] py-[7px] text-[.8125rem] leading-[1.45] font-medium text-tt-ink whitespace-normal shadow-[0_12px_26px_-12px_rgba(59,13,31,.6)] before:absolute before:border-transparent after:absolute before:border-[6px] invisible opacity-0 scale-0 origin-[50%_calc(100%+6px)] tt-open:visible tt-open:opacity-100 tt-open:scale-100 tt-open:[transform:none] tt-open:[filter:none] motion-safe:[transition:opacity_var(--tt-in),visibility_var(--tt-in),transform_var(--tt-in),filter_var(--tt-in),scale_0s_var(--tt-in)] motion-safe:tt-open:[transition:opacity_var(--tt-in),visibility_0s,transform_var(--tt-in)_var(--tt-ease,ease-out),filter_var(--tt-in),scale_0s] motion-reduce:[transition:opacity_.1s,visibility_.1s] motion-reduce:tt-open:[transition:opacity_.1s,visibility_.1s] text-center tt-top bottom-[calc(100%+10px)] left-1/2 translate-x-[calc(-50%+var(--tt-nudge,0px))] before:top-full before:left-[calc(50%-6px-var(--tt-nudge,0px))] before:border-b-0 before:border-t-tt after:inset-x-0 after:top-full after:h-[10px] motion-safe:[transform:translateY(8px)]" role="tooltip" id="fx-3">Only you can see saved posts</span></span><small class="text-xs font-semibold text-[#6b4a57]">Slide up</small></div>
  <div class="flex min-h-[150px] flex-col items-center justify-end gap-2.5 rounded-xl border border-dashed border-[#ecc9d4] bg-[#fffafb] p-3.5"><span class="tt relative inline-flex align-middle [--tt-in:.5s] [--tt-ease:linear(0,.25_8%,.82_24%,1.08_36%,1.1_42%,1.03_56%,.99_68%,1)]"><button class="inline-flex h-[38px] cursor-pointer items-center gap-2 rounded-full border border-[#ecc9d4] bg-white px-3.5 text-sm leading-none font-semibold text-[#2a1520] transition-colors duration-150 hover:bg-[#fdf2f4] motion-reduce:transition-none focus-visible:outline-2 focus-visible:outline-offset-[3px] focus-visible:outline-[#be123c]" type="button" aria-describedby="fx-4"><svg class="size-[17px] text-[#be123c]" aria-hidden="true"><use href="#i-bell"/></svg>Follow</button><span class="pointer-events-auto absolute z-30 w-max max-w-[200px] rounded-[9px] bg-tt px-[11px] py-[7px] text-[.8125rem] leading-[1.45] font-medium text-tt-ink whitespace-normal shadow-[0_12px_26px_-12px_rgba(59,13,31,.6)] before:absolute before:border-transparent after:absolute before:border-[6px] invisible opacity-0 scale-0 origin-[50%_calc(100%+6px)] tt-open:visible tt-open:opacity-100 tt-open:scale-100 tt-open:[transform:none] tt-open:[filter:none] motion-safe:[transition:opacity_var(--tt-in),visibility_var(--tt-in),transform_var(--tt-in),filter_var(--tt-in),scale_0s_var(--tt-in)] motion-safe:tt-open:[transition:opacity_var(--tt-in),visibility_0s,transform_var(--tt-in)_var(--tt-ease,ease-out),filter_var(--tt-in),scale_0s] motion-reduce:[transition:opacity_.1s,visibility_.1s] motion-reduce:tt-open:[transition:opacity_.1s,visibility_.1s] text-center tt-top bottom-[calc(100%+10px)] left-1/2 translate-x-[calc(-50%+var(--tt-nudge,0px))] before:top-full before:left-[calc(50%-6px-var(--tt-nudge,0px))] before:border-b-0 before:border-t-tt after:inset-x-0 after:top-full after:h-[10px] motion-safe:[transform:scale(.5)]" role="tooltip" id="fx-4">Get a ping when Noor posts</span></span><small class="text-xs font-semibold text-[#6b4a57]">Spring</small></div>
  <div class="flex min-h-[150px] flex-col items-center justify-end gap-2.5 rounded-xl border border-dashed border-[#ecc9d4] bg-[#fffafb] p-3.5"><span class="tt relative inline-flex align-middle [--tt-in:.2s]"><button class="inline-flex h-[38px] cursor-pointer items-center gap-2 rounded-full border border-[#ecc9d4] bg-white px-3.5 text-sm leading-none font-semibold text-[#2a1520] transition-colors duration-150 hover:bg-[#fdf2f4] motion-reduce:transition-none focus-visible:outline-2 focus-visible:outline-offset-[3px] focus-visible:outline-[#be123c]" type="button" aria-describedby="fx-5"><svg class="size-[17px] text-[#be123c]" aria-hidden="true"><use href="#i-send"/></svg>Send</button><span class="pointer-events-auto absolute z-30 w-max max-w-[200px] rounded-[9px] bg-tt px-[11px] py-[7px] text-[.8125rem] leading-[1.45] font-medium text-tt-ink whitespace-normal shadow-[0_12px_26px_-12px_rgba(59,13,31,.6)] before:absolute before:border-transparent after:absolute before:border-[6px] invisible opacity-0 scale-0 origin-[50%_calc(100%+6px)] tt-open:visible tt-open:opacity-100 tt-open:scale-100 tt-open:[transform:none] tt-open:[filter:none] motion-safe:[transition:opacity_var(--tt-in),visibility_var(--tt-in),transform_var(--tt-in),filter_var(--tt-in),scale_0s_var(--tt-in)] motion-safe:tt-open:[transition:opacity_var(--tt-in),visibility_0s,transform_var(--tt-in)_var(--tt-ease,ease-out),filter_var(--tt-in),scale_0s] motion-reduce:[transition:opacity_.1s,visibility_.1s] motion-reduce:tt-open:[transition:opacity_.1s,visibility_.1s] text-center tt-top bottom-[calc(100%+10px)] left-1/2 translate-x-[calc(-50%+var(--tt-nudge,0px))] before:top-full before:left-[calc(50%-6px-var(--tt-nudge,0px))] before:border-b-0 before:border-t-tt after:inset-x-0 after:top-full after:h-[10px] motion-safe:[transform:scale(1.06)] motion-safe:[filter:blur(6px)]" role="tooltip" id="fx-5">Send in a direct message</span></span><small class="text-xs font-semibold text-[#6b4a57]">Blur in</small></div>
</div></div>

<div><span class="mb-[14px] block text-[.72rem] tracking-[.12em] text-[#6b4a57] uppercase">Delay on hover, instant on focus</span>
<div class="rounded-[18px] border border-[#f3dce3] bg-white p-7 max-[600px]:p-5">
  <div class="swatches flex flex-wrap gap-3 pt-14" role="group" aria-label="Glaze colours">
    <span class="tt relative inline-flex align-middle [--tt-in:.2s]"><button class="size-11 cursor-pointer rounded-full border-[3px] border-white bg-[#c2410c] p-0 shadow-[0_0_0_1px_#ecc9d4] focus-visible:outline-2 focus-visible:outline-offset-[3px] focus-visible:outline-[#be123c]" type="button" aria-label="Terracotta" aria-describedby="sw-1"></button><span class="pointer-events-auto absolute z-30 w-max max-w-[200px] rounded-[9px] bg-tt px-[11px] py-[7px] text-[.8125rem] leading-[1.45] font-medium text-tt-ink whitespace-normal shadow-[0_12px_26px_-12px_rgba(59,13,31,.6)] before:absolute before:border-transparent after:absolute before:border-[6px] invisible opacity-0 scale-0 origin-[50%_calc(100%+6px)] tt-open:visible tt-open:opacity-100 tt-open:scale-100 tt-open:[transform:none] tt-open:[filter:none] motion-safe:[transition:opacity_var(--tt-in),visibility_var(--tt-in),transform_var(--tt-in),filter_var(--tt-in),scale_0s_var(--tt-in)] motion-safe:tt-open:[transition:opacity_var(--tt-in),visibility_0s,transform_var(--tt-in)_var(--tt-ease,ease-out),filter_var(--tt-in),scale_0s] motion-reduce:[transition:opacity_.1s,visibility_.1s] motion-reduce:tt-open:[transition:opacity_.1s,visibility_.1s] text-center tt-top bottom-[calc(100%+10px)] left-1/2 translate-x-[calc(-50%+var(--tt-nudge,0px))] before:top-full before:left-[calc(50%-6px-var(--tt-nudge,0px))] before:border-b-0 before:border-t-tt after:inset-x-0 after:top-full after:h-[10px] motion-safe:[transform:translateY(8px)] motion-safe:tt-hover:[transition-delay:.4s] motion-reduce:tt-hover:[transition-delay:.4s]" role="tooltip" id="sw-1">Terracotta · in stock</span></span>
    <span class="tt relative inline-flex align-middle [--tt-in:.2s]"><button class="size-11 cursor-pointer rounded-full border-[3px] border-white bg-[#b45309] p-0 shadow-[0_0_0_1px_#ecc9d4] focus-visible:outline-2 focus-visible:outline-offset-[3px] focus-visible:outline-[#be123c]" type="button" aria-label="Ochre" aria-describedby="sw-2"></button><span class="pointer-events-auto absolute z-30 w-max max-w-[200px] rounded-[9px] bg-tt px-[11px] py-[7px] text-[.8125rem] leading-[1.45] font-medium text-tt-ink whitespace-normal shadow-[0_12px_26px_-12px_rgba(59,13,31,.6)] before:absolute before:border-transparent after:absolute before:border-[6px] invisible opacity-0 scale-0 origin-[50%_calc(100%+6px)] tt-open:visible tt-open:opacity-100 tt-open:scale-100 tt-open:[transform:none] tt-open:[filter:none] motion-safe:[transition:opacity_var(--tt-in),visibility_var(--tt-in),transform_var(--tt-in),filter_var(--tt-in),scale_0s_var(--tt-in)] motion-safe:tt-open:[transition:opacity_var(--tt-in),visibility_0s,transform_var(--tt-in)_var(--tt-ease,ease-out),filter_var(--tt-in),scale_0s] motion-reduce:[transition:opacity_.1s,visibility_.1s] motion-reduce:tt-open:[transition:opacity_.1s,visibility_.1s] text-center tt-top bottom-[calc(100%+10px)] left-1/2 translate-x-[calc(-50%+var(--tt-nudge,0px))] before:top-full before:left-[calc(50%-6px-var(--tt-nudge,0px))] before:border-b-0 before:border-t-tt after:inset-x-0 after:top-full after:h-[10px] motion-safe:[transform:translateY(8px)] motion-safe:tt-hover:[transition-delay:.4s] motion-reduce:tt-hover:[transition-delay:.4s]" role="tooltip" id="sw-2">Ochre · in stock</span></span>
    <span class="tt relative inline-flex align-middle [--tt-in:.2s]"><button class="size-11 cursor-pointer rounded-full border-[3px] border-white bg-[#4d7c0f] p-0 shadow-[0_0_0_1px_#ecc9d4] focus-visible:outline-2 focus-visible:outline-offset-[3px] focus-visible:outline-[#be123c]" type="button" aria-label="Moss" aria-describedby="sw-3"></button><span class="pointer-events-auto absolute z-30 w-max max-w-[200px] rounded-[9px] bg-tt px-[11px] py-[7px] text-[.8125rem] leading-[1.45] font-medium text-tt-ink whitespace-normal shadow-[0_12px_26px_-12px_rgba(59,13,31,.6)] before:absolute before:border-transparent after:absolute before:border-[6px] invisible opacity-0 scale-0 origin-[50%_calc(100%+6px)] tt-open:visible tt-open:opacity-100 tt-open:scale-100 tt-open:[transform:none] tt-open:[filter:none] motion-safe:[transition:opacity_var(--tt-in),visibility_var(--tt-in),transform_var(--tt-in),filter_var(--tt-in),scale_0s_var(--tt-in)] motion-safe:tt-open:[transition:opacity_var(--tt-in),visibility_0s,transform_var(--tt-in)_var(--tt-ease,ease-out),filter_var(--tt-in),scale_0s] motion-reduce:[transition:opacity_.1s,visibility_.1s] motion-reduce:tt-open:[transition:opacity_.1s,visibility_.1s] text-center tt-top bottom-[calc(100%+10px)] left-1/2 translate-x-[calc(-50%+var(--tt-nudge,0px))] before:top-full before:left-[calc(50%-6px-var(--tt-nudge,0px))] before:border-b-0 before:border-t-tt after:inset-x-0 after:top-full after:h-[10px] motion-safe:[transform:translateY(8px)] motion-safe:tt-hover:[transition-delay:.4s] motion-reduce:tt-hover:[transition-delay:.4s]" role="tooltip" id="sw-3">Moss · 3 left</span></span>
    <span class="tt relative inline-flex align-middle [--tt-in:.2s]"><button class="size-11 cursor-pointer rounded-full border-[3px] border-white bg-[#0f766e] p-0 shadow-[0_0_0_1px_#ecc9d4] focus-visible:outline-2 focus-visible:outline-offset-[3px] focus-visible:outline-[#be123c]" type="button" aria-label="Lagoon" aria-describedby="sw-4"></button><span class="pointer-events-auto absolute z-30 w-max max-w-[200px] rounded-[9px] bg-tt px-[11px] py-[7px] text-[.8125rem] leading-[1.45] font-medium text-tt-ink whitespace-normal shadow-[0_12px_26px_-12px_rgba(59,13,31,.6)] before:absolute before:border-transparent after:absolute before:border-[6px] invisible opacity-0 scale-0 origin-[50%_calc(100%+6px)] tt-open:visible tt-open:opacity-100 tt-open:scale-100 tt-open:[transform:none] tt-open:[filter:none] motion-safe:[transition:opacity_var(--tt-in),visibility_var(--tt-in),transform_var(--tt-in),filter_var(--tt-in),scale_0s_var(--tt-in)] motion-safe:tt-open:[transition:opacity_var(--tt-in),visibility_0s,transform_var(--tt-in)_var(--tt-ease,ease-out),filter_var(--tt-in),scale_0s] motion-reduce:[transition:opacity_.1s,visibility_.1s] motion-reduce:tt-open:[transition:opacity_.1s,visibility_.1s] text-center tt-top bottom-[calc(100%+10px)] left-1/2 translate-x-[calc(-50%+var(--tt-nudge,0px))] before:top-full before:left-[calc(50%-6px-var(--tt-nudge,0px))] before:border-b-0 before:border-t-tt after:inset-x-0 after:top-full after:h-[10px] motion-safe:[transform:translateY(8px)] motion-safe:tt-hover:[transition-delay:.4s] motion-reduce:tt-hover:[transition-delay:.4s]" role="tooltip" id="sw-4">Lagoon · back in May</span></span>
    <span class="tt relative inline-flex align-middle [--tt-in:.2s]"><button class="size-11 cursor-pointer rounded-full border-[3px] border-white bg-[#6d28d9] p-0 shadow-[0_0_0_1px_#ecc9d4] focus-visible:outline-2 focus-visible:outline-offset-[3px] focus-visible:outline-[#be123c]" type="button" aria-label="Iris" aria-describedby="sw-5"></button><span class="pointer-events-auto absolute z-30 w-max max-w-[200px] rounded-[9px] bg-tt px-[11px] py-[7px] text-[.8125rem] leading-[1.45] font-medium text-tt-ink whitespace-normal shadow-[0_12px_26px_-12px_rgba(59,13,31,.6)] before:absolute before:border-transparent after:absolute before:border-[6px] invisible opacity-0 scale-0 origin-[50%_calc(100%+6px)] tt-open:visible tt-open:opacity-100 tt-open:scale-100 tt-open:[transform:none] tt-open:[filter:none] motion-safe:[transition:opacity_var(--tt-in),visibility_var(--tt-in),transform_var(--tt-in),filter_var(--tt-in),scale_0s_var(--tt-in)] motion-safe:tt-open:[transition:opacity_var(--tt-in),visibility_0s,transform_var(--tt-in)_var(--tt-ease,ease-out),filter_var(--tt-in),scale_0s] motion-reduce:[transition:opacity_.1s,visibility_.1s] motion-reduce:tt-open:[transition:opacity_.1s,visibility_.1s] text-center tt-top bottom-[calc(100%+10px)] left-1/2 translate-x-[calc(-50%+var(--tt-nudge,0px))] before:top-full before:left-[calc(50%-6px-var(--tt-nudge,0px))] before:border-b-0 before:border-t-tt after:inset-x-0 after:top-full after:h-[10px] motion-safe:[transform:translateY(8px)] motion-safe:tt-hover:[transition-delay:.4s] motion-reduce:tt-hover:[transition-delay:.4s]" role="tooltip" id="sw-5">Iris · in stock</span></span>
    <span class="tt relative inline-flex align-middle [--tt-in:.2s]"><button class="size-11 cursor-pointer rounded-full border-[3px] border-white bg-[#1e293b] p-0 shadow-[0_0_0_1px_#ecc9d4] focus-visible:outline-2 focus-visible:outline-offset-[3px] focus-visible:outline-[#be123c]" type="button" aria-label="Slate" aria-describedby="sw-6"></button><span class="pointer-events-auto absolute z-30 w-max max-w-[200px] rounded-[9px] bg-tt px-[11px] py-[7px] text-[.8125rem] leading-[1.45] font-medium text-tt-ink whitespace-normal shadow-[0_12px_26px_-12px_rgba(59,13,31,.6)] before:absolute before:border-transparent after:absolute before:border-[6px] invisible opacity-0 scale-0 origin-[50%_calc(100%+6px)] tt-open:visible tt-open:opacity-100 tt-open:scale-100 tt-open:[transform:none] tt-open:[filter:none] motion-safe:[transition:opacity_var(--tt-in),visibility_var(--tt-in),transform_var(--tt-in),filter_var(--tt-in),scale_0s_var(--tt-in)] motion-safe:tt-open:[transition:opacity_var(--tt-in),visibility_0s,transform_var(--tt-in)_var(--tt-ease,ease-out),filter_var(--tt-in),scale_0s] motion-reduce:[transition:opacity_.1s,visibility_.1s] motion-reduce:tt-open:[transition:opacity_.1s,visibility_.1s] text-center tt-top bottom-[calc(100%+10px)] left-1/2 translate-x-[calc(-50%+var(--tt-nudge,0px))] before:top-full before:left-[calc(50%-6px-var(--tt-nudge,0px))] before:border-b-0 before:border-t-tt after:inset-x-0 after:top-full after:h-[10px] motion-safe:[transform:translateY(8px)] motion-safe:tt-hover:[transition-delay:.4s] motion-reduce:tt-hover:[transition-delay:.4s]" role="tooltip" id="sw-6">Slate · in stock</span></span>
  </div>
</div></div>
CSS
@theme {
  --color-tt: #3b0d1f;
  --color-tt-ink: #fff5f8;
}
/* tt-open: a tooltip inside a .tt wrapper that is hovered, holds focus or was opened by tap, and was not dismissed with Esc. */
@custom-variant tt-open (&:is(:where(.tt):is(:hover, :focus-within, .is-open):not(.is-dismissed) > *));
/* tt-hover: hovered but not focused, so a delayed tooltip waits before it appears. */
@custom-variant tt-hover (&:is(:where(.tt):hover:not(:focus-within, .is-open, .is-dismissed) > *));
JavaScript
// Tooltip behaviour for WCAG 1.4.13: Esc dismisses without moving focus or the pointer, a tap opens it on touch screens,
// opening one closes the others, and tooltips are nudged sideways to stay inside the window.
(() => {
  const tts = [...document.querySelectorAll('.tt')];
  const fit = t => {
    const tip = t.querySelector(':scope > [role="tooltip"]'); if (!tip) return;
    tip.style.removeProperty('--tt-nudge');
    requestAnimationFrame(() => {
      const r = tip.getBoundingClientRect(), w = document.documentElement.clientWidth;
      const d = r.left < 8 ? 8 - r.left : r.right > w - 8 ? w - 8 - r.right : 0;
      if (d) tip.style.setProperty('--tt-nudge', d + 'px');
    });
  };
  const enter = t => { tts.forEach(o => o !== t && o.classList.remove('is-open')); t.classList.remove('is-dismissed'); fit(t); };
  tts.forEach(t => { t.addEventListener('pointerenter', () => enter(t)); t.addEventListener('focusin', () => enter(t)); if (t.classList.contains('is-open')) fit(t); });
  document.addEventListener('pointerdown', e => {
    const t = e.target.closest('.tt');
    if (!t) tts.forEach(o => o.classList.remove('is-open'));
    else if (e.pointerType === 'touch') t.classList.add('is-open');
  });
  document.addEventListener('keydown', e => {
    if (e.key !== 'Escape') return;
    tts.filter(t => t.matches(':hover, :focus-within, .is-open')).forEach(t => { t.classList.remove('is-open'); t.classList.add('is-dismissed'); });
  });
})();

About this set

Tooltip entrances that stay quick and calm. Five entrances gives each of five social buttons a different animation: Like fades, Repost scales up from the tip of its arrow, Save slides up eight pixels, Follow springs in with a slight overshoot drawn by the CSS linear() easing function, and Send sharpens from a blur. Delay on hover, instant on focus is a row of glaze colour swatches for a ceramics shop whose tooltips wait 400 milliseconds before appearing on hover, so sweeping the pointer across the row does not flash six labels, while keyboard focus shows them at once. Every entrance is one class that sets the hidden state, such as transform: scale(.8); the open state always returns to no transform, so mixing entrances needs no extra rules. All of them last 200 milliseconds except the spring. Under prefers-reduced-motion the movement, scaling and blur are removed and only a 100 millisecond fade remains. The Bootstrap version runs the same entrances as keyframes on Bootstrap Tooltip, using the separate scale and translate properties so they add to the position Popper sets.

What’s included

  • Fade, scale, slide, spring and blur entrances
  • Spring easing written with linear()
  • Hover delay of 400 ms with instant keyboard focus
  • One class per entrance
  • Only a short fade under prefers-reduced-motion
  • Bootstrap Tooltip with keyframe entrances

Accessibility

Each button is linked to its tooltip with aria-describedby and each swatch has an aria-label with its colour name. Tooltips open on hover and keyboard focus, Esc hides them, and they stay open under the pointer. The hover delay never applies to keyboard focus. With prefers-reduced-motion set, every transform, scale and blur is removed and a 100 ms fade is all that remains.

Customise it

Change the duration with --tt-in and the curve with --tt-ease on .tt, and the hidden state in the .fx-* rules. In Bootstrap edit the @keyframes and the animation rules on .tooltip.fx-*, and data-tt-delay sets the hover delay. In Tailwind edit the [--tt-in:...] and [transform:...] utilities.