# Tooltip / Tooltip

> Short label on hover or focus.

Page: https://componentes-lovat.vercel.app/c/tooltip/  
License: MIT · Design: coss ui (MIT) · tooltip.tsx  
Requires: tokens.css, button  
Context: ambos · Density: media · Tone: sobria

## Files

- Tokens (once): https://componentes-lovat.vercel.app/tokens.css
- Dependency `button`: https://componentes-lovat.vercel.app/r/button.json
- HTML: https://componentes-lovat.vercel.app/raw/tooltip/tooltip.html
- CSS: https://componentes-lovat.vercel.app/raw/tooltip/tooltip.css
- JS: https://componentes-lovat.vercel.app/raw/tooltip/tooltip.js
- Everything in one file: https://componentes-lovat.vercel.app/r/tooltip.html

Dark theme: add class `dark` to `<html>`.

## When to use

- To name icon buttons or show a shortcut.

## When not to use

- For important information: touch screens have no hover.

## Accessibility

Roles: tooltip

| Keys | Action |
|---|---|
| Tab | Shows the tooltip on focus |
| Esc | Hides it |

- The text is also in aria-describedby.
- You can hover the tooltip without it disappearing (WCAG 1.4.13).

## Code

```html
<div class="c-tooltip-row">
  <button class="c-btn c-btn--outline c-btn--icon" type="button" aria-label="Negrita" aria-describedby="tip-bold" data-c-tooltip="tip-bold">
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 12h9a4 4 0 0 1 0 8H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h7a4 4 0 0 1 0 8"/></svg>
  </button>
  <button class="c-btn c-btn--outline c-btn--icon" type="button" aria-label="Cursiva" aria-describedby="tip-italic" data-c-tooltip="tip-italic">
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M19 4h-9"/><path d="M14 20H5"/><path d="M15 4 9 20"/></svg>
  </button>
  <button class="c-btn c-btn--outline" type="button" aria-describedby="tip-save" data-c-tooltip="tip-save">Guardar</button>
</div>
<div class="c-tooltip" id="tip-bold" role="tooltip" popover="manual">Negrita · ⌘B</div>
<div class="c-tooltip" id="tip-italic" role="tooltip" popover="manual">Cursiva · ⌘I</div>
<div class="c-tooltip" id="tip-save" role="tooltip" popover="manual">Se guarda en tu navegador</div>
```

```css
/* tooltip · Componentes (MIRÓ Labs). Diseño de coss ui (MIT, © coss.com, antes Origin UI).
   Generado por tools/build_css.py desde tooltip.tw.json. No editar a mano. Necesita tokens.css. */
.c-tooltip{position:relative;display:flex;border-radius:calc(var(--radius) - 2px);border-style:var(--tw-border-style);border-width:1px;background-color:var(--popover);padding-inline:calc(var(--spacing)*2);padding-block:calc(var(--spacing)*1);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));text-wrap:balance;color:var(--popover-foreground);--tw-shadow-alpha:5%;--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000000d),0 2px 4px -2px var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}
@supports (color:lab(0% 0 0)){
.c-tooltip{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,lab(0% 0 0/.05)),0 2px 4px -2px var(--tw-shadow-color,lab(0% 0 0/.05))}
}
.c-tooltip{transition-property:width,height,scale,opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}
.c-tooltip:not(:is(.dark *)){background-clip:padding-box}
.c-tooltip:before{content:var(--tw-content);pointer-events:none;content:var(--tw-content);position:absolute;content:var(--tw-content);inset:calc(var(--spacing)*0);content:var(--tw-content);border-radius:calc(var(--radius-md) - 1px);content:var(--tw-content);--tw-shadow:0 1px var(--tw-shadow-color,#0000000a)}
@supports (color:color-mix(in lab, red, red)){
.c-tooltip:before{--tw-shadow:0 1px var(--tw-shadow-color,color-mix(in oklab,var(--color-black)4%,transparent))}
}
.c-tooltip:before{box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}
.c-tooltip:is(.dark *):before{content:var(--tw-content);--tw-shadow:0 -1px var(--tw-shadow-color,#ffffff0f)}
@supports (color:color-mix(in lab, red, red)){
.c-tooltip:is(.dark *):before{--tw-shadow:0 -1px var(--tw-shadow-color,color-mix(in oklab,var(--color-white)6%,transparent))}
}
.c-tooltip:is(.dark *):before{box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}
/* propio */
.c-tooltip{position:fixed;margin:0;inset:auto;max-width:16rem;color:var(--popover-foreground);opacity:0;scale:.98;transition:opacity .15s,scale .15s,overlay .15s allow-discrete,display .15s allow-discrete}
.c-tooltip:popover-open{opacity:1;scale:1}
@starting-style{.c-tooltip:popover-open{opacity:0;scale:.98}}
.c-tooltip-row{display:flex;flex-wrap:wrap;gap:.5rem;align-items:center}
```

```js
/* Tooltip: aparece al pasar el ratón (tras 400 ms) o al enfocar con teclado; Esc lo cierra.
   Se puede pasar el ratón encima sin que se cierre (WCAG 1.4.13). El texto también va en aria-describedby. */
(function () {
  const C = (window.Componentes = window.Componentes || {});
  const DELAY = 400, SKIP = 300, HIDE = 100;
  let lastHidden = 0, current = null;

  function place(trigger, tip) {
    const r = trigger.getBoundingClientRect(), w = tip.offsetWidth, h = tip.offsetHeight;
    const above = r.top - 6 - h >= 0;
    tip.style.top = (above ? r.top - 6 - h : r.bottom + 6) + 'px';
    tip.style.left = Math.max(8, Math.min(r.left + r.width / 2 - w / 2, innerWidth - w - 8)) + 'px';
    tip.style.transformOrigin = above ? 'bottom center' : 'top center';
  }

  function setup(trigger) {
    const tip = document.getElementById(trigger.dataset.cTooltip);
    if (!tip || !tip.showPopover) return;
    let showT, hideT;
    const show = (now) => {
      clearTimeout(hideT);
      clearTimeout(showT);
      const run = () => {
        if (current && current !== tip && current.matches(':popover-open')) current.hidePopover();
        if (!tip.matches(':popover-open')) tip.showPopover();
        place(trigger, tip);
        current = tip;
      };
      if (now || Date.now() - lastHidden < SKIP) run(); else showT = setTimeout(run, DELAY);
    };
    const hide = () => {
      clearTimeout(showT);
      hideT = setTimeout(() => { if (tip.matches(':popover-open')) { tip.hidePopover(); lastHidden = Date.now(); } }, HIDE);
    };
    trigger.addEventListener('pointerenter', (e) => { if (e.pointerType === 'mouse') show(false); });
    trigger.addEventListener('pointerleave', hide);
    tip.addEventListener('pointerenter', () => clearTimeout(hideT));
    tip.addEventListener('pointerleave', hide);
    trigger.addEventListener('focus', () => { if (trigger.matches(':focus-visible')) show(true); });
    trigger.addEventListener('blur', hide);
    trigger.addEventListener('pointerdown', () => { clearTimeout(showT); if (tip.matches(':popover-open')) tip.hidePopover(); });
    document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && tip.matches(':popover-open')) { tip.hidePopover(); lastHidden = Date.now(); } });
  }

  function init(root) {
    (root || document).querySelectorAll('[data-c-tooltip]:not([data-c-ready])').forEach((t) => {
      t.dataset.cReady = '';
      try { setup(t); } catch (e) { console.warn('[tooltip]', e); }
    });
  }

  C.tooltip = { init };
  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', () => init());
  else init();
})();
```

## Credits

- coss ui (MIT) · tooltip.tsx: https://coss.com/ui
- W3C WAI-ARIA APG, Tooltip: https://www.w3.org/WAI/ARIA/apg/patterns/tooltip/
