Tooltip
Small pop-up element that provides additional information when users hover or focus on an element.
-
Default
Tooltips are tricky because of all the accessibility concerns. We recommend using a 3rd party solution like Tippy.js because it takes care of the anchoring, a11y, and keeps the tooltip inside the viewport.
<div class="gap-4 grid items-center grid-cols-2 [&_[data-tippy-root]]:inline-block md:grid-cols-4"> <!-- <- this is just for demo purposes --> <div> <div data-tippy-root> <div class="tippy-box" data-placement="top"> <div class="tippy-backdrop"></div> <div class="tippy-arrow"></div> <div class="tippy-content">Top</div> </div> </div> <div class="mt-1 pl-4 text-sm">Top</div> <!-- this is for demo purposes --> </div> <div> <div class="mb-1 pl-4 text-sm">Bottom</div> <!-- this is for demo purposes --> <div data-tippy-root> <div class="tippy-box" data-placement="bottom"> <div class="tippy-backdrop"></div> <div class="tippy-arrow"></div> <div class="tippy-content">Bottom</div> </div> </div> </div> <div> <div data-tippy-root> <div class="tippy-box" data-placement="left"> <div class="tippy-backdrop"></div> <div class="tippy-arrow"></div> <div class="tippy-content">Left</div> </div> </div> <span class="ml-2 text-sm">Left</span> <!-- this is for demo purposes --> </div> <div> <span class="mr-2 text-sm">Right</span> <!-- this is for demo purposes --> <div data-tippy-root> <div class="tippy-box" data-placement="right"> <div class="tippy-backdrop"></div> <div class="tippy-arrow"></div> <div class="tippy-content">Right</div> </div> </div> </div> </div> -
<button class="prs-btn prs-btn-primary" x-tooltip="'Tooltip'">Hover/Focus for Tooltip</button> <button class="prs-btn prs-btn-primary" @mouseenter="$tooltip('Tooltip')">Hover Only</button> <button class="prs-btn prs-btn-primary" @focus="$tooltip('Tooltip')">Focus Only</button>import Alpine from 'alpinejs' import tippy from 'tippy.js' document.addEventListener('alpine:init', () => { Alpine.magic('tooltip', el => message => { let instance = tippy(el, { content: message, trigger: 'manual', }); // Add Escape handler *before* showing the tooltip const onEscape = (e) => { if (e.key === 'Escape') cleanup(); }; const cleanup = () => { instance.hide(); setTimeout(() => instance.destroy(), 150); document.removeEventListener('keydown', onEscape); el.removeEventListener('mouseleave', cleanup); el.removeEventListener('blur', cleanup); }; document.addEventListener('keydown', onEscape); el.addEventListener('mouseleave', cleanup); el.addEventListener('blur', cleanup); instance.show(); // Optional fallback timeout setTimeout(cleanup, 2000); }); Alpine.directive('tooltip', (el, { expression }, { evaluate }) => { const instance = tippy(el, { content: evaluate(expression), }); const onEscape = (e) => { if (e.key === 'Escape') { instance.hide(); } }; document.addEventListener('keydown', onEscape); el._tippyCleanup = () => { document.removeEventListener('keydown', onEscape); instance.destroy(); }; }); document.addEventListener('alpine:clean', (e) => { const el = e.target; if (el._tippyCleanup) { el._tippyCleanup(); delete el._tippyCleanup; } }); });
CSS
Full Library
Component Only
/* tippy.js tooltips */
.tippy-box {
background-color: var(--prs-c-gray-900);
color: var(--prs-c-white);
white-space: normal;
outline: 0;
position: relative;
opacity: 1;
box-shadow: 0 0 0 1px rgb(255 255 255), 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -1px rgb(0 0 0 / 0.06);
border-radius: 0.25rem;
transform: translateY(0);
transition-property: var(--prs-transition-property);
transition-timing-function: var(--prs-transition-timing);
transition-duration: var(--prs-transition-duration);
&[data-state="hidden"] {
opacity: 0;
transform: translateY(0.25rem);
}
&[data-placement^="top"],&[data-placement^="bottom"],&[data-placement^="left"],&[data-placement^="right"] {
> .tippy-arrow {
width: 1rem;
height: 1rem;
color: var(--prs-c-gray-900);
position: absolute;
&::before {
content: '';
position: absolute;
border-style: solid;
border-color: transparent;
}
}
}
&[data-placement^="top"] > .tippy-arrow {
filter: drop-shadow(0 1px 0 rgb(255 255 255));
bottom: 0;
transform: translateX(-50%);
left: 50%;
transform-origin: top;
&::before {
bottom: -7px;
left: 0;
border-top: 8px solid currentColor;
border-right: 8px solid transparent;
border-bottom: 0;
border-left: 8px solid transparent;
}
}
&[data-placement^="bottom"] > .tippy-arrow {
filter: drop-shadow(0 -1px 0 rgb(255 255 255));
top: 0;
transform: translateX(-50%);
left: 50%;
transform-origin: bottom;
&::before {
top: -7px;
left: 0;
border-top: 0;
border-right: 8px solid transparent;
border-bottom: 8px solid currentColor;
border-left: 8px solid transparent;
}
}
&[data-placement^="left"] > .tippy-arrow {
right: 0;
filter: drop-shadow(1px 0 0 rgb(255 255 255));
transform: translateY(-50%);
top: 50%;
transform-origin: left;
&::before {
right: -7px;
border-top: 8px solid transparent;
border-right: 0;
border-bottom: 8px solid transparent;
border-left: 8px solid currentColor;
}
}
&[data-placement^="right"] > .tippy-arrow {
left: 0;
filter: drop-shadow(-1px 0 0 rgb(255 255 255));
transform: translateY(-50%);
top: 50%;
transform-origin: right;
&::before {
left: -7px;
border-top: 8px solid transparent;
border-right: 8px solid currentColor;
border-bottom: 8px solid transparent;
border-left: 0;
}
}
}
[data-tippy-root] {
max-width: calc(100vw - 10px);
}
.tippy-content {
padding: 0.5rem 1rem;
font-size: 16px;
line-height: 24px;
position: relative;
z-index: 1;
@media (min-width: 768px) {
padding: 0.25rem 1rem;
font-size: 12px;
line-height: 18px;
}
}
Coming soon...