get started
theming
components
interactive
A tooltip is a popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it.
<button
class="btn variant-destructive icon-only sz-md"
aria-describedby="tooltip-1"
data-tooltip-position="top"
data-tooltip="tooltip-1"
data-tooltip-content="Top tooltip"
data-tooltip-offset = "4"
>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4">
<path stroke-linecap="round" stroke-linejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
</svg>
<span class="sr-only">Delete</span>
</button>
To install the Tooltip component, copy and paste the following typescript code into your project.
document.addEventListener('DOMContentLoaded', () => {
const triggers = document.querySelectorAll('[data-tooltip]') as NodeListOf<HTMLElement>;
let activeTooltip: HTMLElement | null = null;
triggers.forEach((trigger) => {
const tooltipTemplate = document.getElementById('tooltip-template') as HTMLTemplateElement;
const tooltip = document.importNode(tooltipTemplate.content, true).firstElementChild as HTMLElement;
const content = trigger.getAttribute('data-tooltip-content');
const position = trigger.getAttribute('data-tooltip-position') || 'top';
const id = trigger.getAttribute('aria-describedby');
const offset = parseInt(trigger.getAttribute('data-tooltip-offset') || '8');
if (content) tooltip.textContent = content;
tooltip.setAttribute('id', id as string);
tooltip.style.position = 'fixed';
tooltip.style.transform = position === 'top' ? 'translateY(0.5rem)' : position === 'bottom' ? 'translateY(-0.5rem)' : position === 'left' ? 'translateX(0.5rem)' : 'translateX(-0.5rem)';
document.body.appendChild(tooltip);
const updateTooltipPosition = () => {
const triggerRect = trigger.getBoundingClientRect();
const tooltipRect = tooltip.getBoundingClientRect();
const positions = {
top: {
top: triggerRect.top - tooltipRect.height - offset,
left: triggerRect.left + (triggerRect.width - tooltipRect.width) / 2,
},
bottom: {
top: triggerRect.bottom + offset,
left: triggerRect.left + (triggerRect.width - tooltipRect.width) / 2,
},
left: {
top: triggerRect.top + (triggerRect.height - tooltipRect.height) / 2,
left: triggerRect.left - tooltipRect.width - offset,
},
right: {
top: triggerRect.top + (triggerRect.height - tooltipRect.height) / 2,
left: triggerRect.right + offset,
},
};
const preferredCoords = positions[position as keyof typeof positions];
const fallbackPosition = getFallbackPosition(position);
const fallbackCoords = positions[fallbackPosition];
const finalCoords = isInViewport(preferredCoords, tooltipRect)
? preferredCoords
: fallbackCoords;
tooltip.style.top = `${finalCoords.top}px`;
tooltip.style.left = `${finalCoords.left}px`;
};
const showTooltip = () => {
tooltip.setAttribute('data-tooltip-position', position);
tooltip.style.visibility = 'visible';
tooltip.style.opacity = '1';
tooltip.style.transform = 'translateY(0)';
activeTooltip = tooltip;
updateTooltipPosition();
window.addEventListener('scroll', updateTooltipPosition);
window.addEventListener('resize', updateTooltipPosition);
};
const hideTooltip = () => {
tooltip.style.visibility = 'hidden';
tooltip.style.opacity = '0';
tooltip.style.transform = position === 'top' ? 'translateY(0.5rem)' : position === 'bottom' ? 'translateY(-0.5rem)' : position === 'left' ? 'translateX(0.5rem)' : 'translateX(-0.5rem)';
if (activeTooltip === tooltip) activeTooltip = null;
window.removeEventListener('scroll', updateTooltipPosition);
window.removeEventListener('resize', updateTooltipPosition);
};
let isHoveringTrigger = false;
let isHoveringTooltip = false;
const conditionalHide = () => {
if (!isHoveringTrigger && !isHoveringTooltip) {
hideTooltip();
}
};
trigger.addEventListener('mouseenter', () => {
isHoveringTrigger = true;
showTooltip();
});
trigger.addEventListener('mouseleave', () => {
isHoveringTrigger = false;
setTimeout(conditionalHide, 100); // Small delay to handle mouse movement
});
tooltip.addEventListener('mouseenter', () => {
isHoveringTooltip = true;
showTooltip();
});
tooltip.addEventListener('mouseleave', () => {
isHoveringTooltip = false;
setTimeout(conditionalHide, 100);
});
trigger.addEventListener('focus', showTooltip);
trigger.addEventListener('blur', hideTooltip);
});
document.addEventListener('keydown', (event) => {
if (event.key === 'Escape' && activeTooltip) {
activeTooltip.style.visibility = 'hidden';
activeTooltip.style.opacity = '0';
activeTooltip = null;
}
});
function getFallbackPosition(position: string) {
switch (position) {
case 'top':
return 'bottom';
case 'bottom':
return 'top';
case 'left':
return 'right';
case 'right':
return 'left';
default:
return 'top';
}
}
function isInViewport(coords: { top: number; left: number }, tooltipRect: DOMRect) {
const { top, left } = coords;
return (
top >= 36 &&
left >= 36 &&
top + tooltipRect.height <= window.innerHeight &&
left + tooltipRect.width <= window.innerWidth
);
}
});
Copy and paste the following HTML code into your project to create a tooltip template, preferably at the end of the <body>
tag.
<template id="tooltip-template">
<div
role="tooltip"
data-rounded="large"
class="card variant-mixed px-3 text-sm py-1.5 z-[100] size-fit fixed transition-[opacity,scale,transform] duration-150 ease-in-out"
>
</div>
</template>
<button
class="btn variant-destructive icon-only sz-md"
aria-describedby="tooltip-2"
data-tooltip-position="bottom"
data-tooltip="tooltip-2"
data-tooltip-content="Bottom tooltip"
data-tooltip-offset = "4"
>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4">
<path stroke-linecap="round" stroke-linejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
</svg>
<span class="sr-only">Delete</span>
</button>
<button
class="btn variant-destructive icon-only sz-md"
aria-describedby="tooltip-3"
data-tooltip-position="left"
data-tooltip="tooltip-3"
data-tooltip-content="Left tooltip"
data-tooltip-offset = "4"
>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4">
<path stroke-linecap="round" stroke-linejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
</svg>
<span class="sr-only">Delete</span>
</button>
<button
class="btn variant-destructive icon-only sz-md"
aria-describedby="tooltip-4"
data-tooltip-position="right"
data-tooltip="tooltip-4"
data-tooltip-content="Right tooltip"
data-tooltip-offset = "4"
>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4">
<path stroke-linecap="round" stroke-linejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
</svg>
<span class="sr-only">Delete</span>
</button>