Tooltip
This library is a work-in-progress. We are releasing it early to gather feedback, but it is not ready for production.
A flexible, accessible tooltip component that displays contextual information on hover, focus, or click.
Examples
Live Preview
Basic Tooltip
Placement Options
Trigger Options
Icon-Only Buttons
CSS Shadow Parts Customization
Use CSS Shadow Parts to customize the tooltip's appearance without affecting the component's internal styling. One drawback is that the arrow part can be tricky to style due to its border-based implementation so we hide it in this example.
View Vue Code
<template>
<section>
<div class="mbe4">
<h2>Basic Tooltip</h2>
</div>
<div class="stacked-mobile mbe4">
<VueTooltip content="This is a tooltip">
<VueButton>Hover me</VueButton>
</VueTooltip>
<VueTooltip content="Tooltips provide helpful context">
<VueButton variant="primary">Primary button</VueButton>
</VueTooltip>
<VueTooltip content="Additional information here">
<VueButton variant="success">Success button</VueButton>
</VueTooltip>
</div>
<div class="mbe4">
<h2>Placement Options</h2>
</div>
<div class="stacked-mobile mbe4">
<VueTooltip
content="Tooltip on top"
placement="top"
>
<VueButton>Top</VueButton>
</VueTooltip>
<VueTooltip
content="Tooltip on bottom"
placement="bottom"
>
<VueButton>Bottom</VueButton>
</VueTooltip>
<VueTooltip
content="Tooltip on left"
placement="left"
>
<VueButton>Left</VueButton>
</VueTooltip>
<VueTooltip
content="Tooltip on right"
placement="right"
>
<VueButton>Right</VueButton>
</VueTooltip>
</div>
<div class="mbe4">
<h2>Trigger Options</h2>
</div>
<div class="stacked-mobile mbe4">
<VueTooltip
content="Hover over me"
trigger="hover"
>
<VueButton>Hover trigger</VueButton>
</VueTooltip>
<VueTooltip
content="Click to toggle"
trigger="click"
>
<VueButton>Click trigger</VueButton>
</VueTooltip>
<VueTooltip
content="Hover or focus works"
trigger="hover focus"
>
<VueButton>Hover + Focus</VueButton>
</VueTooltip>
</div>
<div class="mbe4">
<h2>Icon-Only Buttons</h2>
</div>
<div class="stacked-mobile mbe4">
<VueTooltip content="Edit item">
<VueButton aria-label="Edit">
<Pencil
color="var(--ag-secondary)"
:size="18"
/>
</VueButton>
</VueTooltip>
<VueTooltip content="Delete item">
<VueButton
variant="danger"
aria-label="Delete"
>
<Trash2
color="var(--ag-white)"
:size="18"
/>
</VueButton>
</VueTooltip>
<VueTooltip content="Download file">
<VueButton
variant="success"
aria-label="Download"
>
<Download
color="var(--ag-white)"
:size="18"
/>
</VueButton>
</VueTooltip>
<VueTooltip content="Share content">
<VueButton
variant="secondary"
aria-label="Share"
>
<Share2
color="var(--ag-neutral-700)"
:size="18"
/>
</VueButton>
</VueTooltip>
</div>
<div class="mbe4">
<h2>CSS Shadow Parts Customization</h2>
<p class="mbs2 mbe3">
Use CSS Shadow Parts to customize the tooltip's appearance without affecting the component's internal styling. One drawback is that the arrow part can be tricky to style due to its border-based implementation so we hide it in this example.
</p>
</div>
<div class="stacked-mobile mbe4">
<VueTooltip
class="custom-tooltip-gradient"
content="Customized with CSS Shadow Parts!"
placement="top"
>
<VueButton variant="primary">Gradient Tooltip</VueButton>
</VueTooltip>
</div>
</section>
</template>
<script>
import VueTooltip from "agnosticui-core/tooltip/vue";
import VueButton from "agnosticui-core/button/vue";
import { Pencil, Trash2, Download, Share2 } from "lucide-vue-next";
export default {
name: "TooltipExamples",
components: {
VueTooltip,
VueButton,
Pencil,
Trash2,
Download,
Share2,
},
};
</script>
<style>
/* CSS Shadow Parts customization examples */
.custom-tooltip-gradient::part(ag-tooltip) {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 12px;
padding: 12px 16px;
font-weight: 600;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
max-width: 250px;
}
/* The border-based arrow, floating-ui's flip, and other complexities makes
the ROI on having an arrow questionable. So, we just hide ¯\_(ツ)_/¯ */
.custom-tooltip-gradient::part(ag-tooltip-arrow) {
display: none;
}
</style>
Live Preview
View Lit / Web Component Code
import { LitElement, html } from 'lit';
import 'agnosticui-core/tooltip';
import 'agnosticui-core/button';
export class TooltipLitExamples extends LitElement {
createRenderRoot() {
return this;
}
render() {
return html`
<section>
<div class="mbe4">
<h2>Basic Tooltip</h2>
</div>
<div class="stacked-mobile mbe4">
<ag-tooltip content="This is a tooltip">
<ag-button>Hover me</ag-button>
</ag-tooltip>
<ag-tooltip content="Tooltips provide helpful context">
<ag-button variant="primary">Primary button</ag-button>
</ag-tooltip>
<ag-tooltip content="Additional information here">
<ag-button variant="success">Success button</ag-button>
</ag-tooltip>
</div>
<div class="mbe4">
<h2>Placement Options</h2>
</div>
<div class="stacked-mobile mbe4">
<ag-tooltip
content="Tooltip on top"
placement="top"
>
<ag-button>Top</ag-button>
</ag-tooltip>
<ag-tooltip
content="Tooltip on bottom"
placement="bottom"
>
<ag-button>Bottom</ag-button>
</ag-tooltip>
<ag-tooltip
content="Tooltip on left"
placement="left"
>
<ag-button>Left</ag-button>
</ag-tooltip>
<ag-tooltip
content="Tooltip on right"
placement="right"
>
<ag-button>Right</ag-button>
</ag-tooltip>
</div>
<div class="mbe4">
<h2>Trigger Options</h2>
</div>
<div class="stacked-mobile mbe4">
<ag-tooltip
content="Hover over me"
trigger="hover"
>
<ag-button>Hover trigger</ag-button>
</ag-tooltip>
<ag-tooltip
content="Click to toggle"
trigger="click"
>
<ag-button>Click trigger</ag-button>
</ag-tooltip>
<ag-tooltip
content="Hover or focus works"
trigger="hover focus"
>
<ag-button>Hover + Focus</ag-button>
</ag-tooltip>
</div>
<div class="mbe4">
<h2>Icon-Only Buttons</h2>
</div>
<div class="stacked-mobile mbe4">
<ag-tooltip content="Edit item">
<ag-button aria-label="Edit">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="var(--ag-secondary)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z"></path></svg>
</ag-button>
</ag-tooltip>
<ag-tooltip content="Delete item">
<ag-button
variant="danger"
aria-label="Delete"
>
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="var(--ag-white)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"></path><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path><path d="M10 11v6"></path><path d="M14 11v6"></path></svg>
</ag-button>
</ag-tooltip>
<ag-tooltip content="Download file">
<ag-button
variant="success"
aria-label="Download"
>
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="var(--ag-white)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>
</ag-button>
</ag-tooltip>
<ag-tooltip content="Share content">
<ag-button
variant="secondary"
aria-label="Share"
>
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="var(--ag-neutral-700)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"></path><polyline points="16 6 12 2 8 6"></polyline><line x1="12" y1="2" x2="12" y2="15"></line></svg>
</ag-button>
</ag-tooltip>
</div>
<div class="mbe4">
<h2>CSS Shadow Parts Customization</h2>
<p class="mbs2 mbe3">
Use CSS Shadow Parts to customize the tooltip's appearance without affecting the component's internal styling. One drawback is that the arrow part can be tricky to style due to its border-based implementation so we hide it in this example.
</p>
</div>
<div class="stacked-mobile mbe4">
<ag-tooltip
class="custom-tooltip-gradient"
content="Customized with CSS Shadow Parts!"
placement="top"
>
<ag-button variant="primary">Gradient Tooltip</ag-button>
</ag-tooltip>
</div>
</section>
`;
}
}
customElements.define('tooltip-lit-examples', TooltipLitExamples);
Interactive Preview: Click the "Open in StackBlitz" button below to see this example running live in an interactive playground.
View React Code
import { ReactTooltip } from "agnosticui-core/tooltip/react";
import { ReactButton } from "agnosticui-core/button/react";
import { Pencil, Trash2, Download, Share2 } from "lucide-react";
export default function TooltipReactExamples() {
return (
<section>
<div className="mbe4">
<h2>Basic Tooltip</h2>
</div>
<div className="stacked-mobile mbe4">
<ReactTooltip content="This is a tooltip">
<ReactButton>Hover me</ReactButton>
</ReactTooltip>
<ReactTooltip content="Tooltips provide helpful context">
<ReactButton variant="primary">Primary button</ReactButton>
</ReactTooltip>
<ReactTooltip content="Additional information here">
<ReactButton variant="success">Success button</ReactButton>
</ReactTooltip>
</div>
<div className="mbe4">
<h2>Placement Options</h2>
</div>
<div className="stacked-mobile mbe4">
<ReactTooltip
content="Tooltip on top"
placement="top"
>
<ReactButton>Top</ReactButton>
</ReactTooltip>
<ReactTooltip
content="Tooltip on bottom"
placement="bottom"
>
<ReactButton>Bottom</ReactButton>
</ReactTooltip>
<ReactTooltip
content="Tooltip on left"
placement="left"
>
<ReactButton>Left</ReactButton>
</ReactTooltip>
<ReactTooltip
content="Tooltip on right"
placement="right"
>
<ReactButton>Right</ReactButton>
</ReactTooltip>
</div>
<div className="mbe4">
<h2>Trigger Options</h2>
</div>
<div className="stacked-mobile mbe4">
<ReactTooltip
content="Hover over me"
trigger="hover"
>
<ReactButton>Hover trigger</ReactButton>
</ReactTooltip>
<ReactTooltip
content="Click to toggle"
trigger="click"
>
<ReactButton>Click trigger</ReactButton>
</ReactTooltip>
<ReactTooltip
content="Hover or focus works"
trigger="hover focus"
>
<ReactButton>Hover + Focus</ReactButton>
</ReactTooltip>
</div>
<div className="mbe4">
<h2>Icon-Only Buttons</h2>
</div>
<div className="stacked-mobile mbe4">
<ReactTooltip content="Edit item">
<ReactButton aria-label="Edit">
<Pencil
color="var(--ag-secondary)"
size={18}
/>
</ReactButton>
</ReactTooltip>
<ReactTooltip content="Delete item">
<ReactButton
variant="danger"
aria-label="Delete"
>
<Trash2
color="var(--ag-white)"
size={18}
/>
</ReactButton>
</ReactTooltip>
<ReactTooltip content="Download file">
<ReactButton
variant="success"
aria-label="Download"
>
<Download
color="var(--ag-white)"
size={18}
/>
</ReactButton>
</ReactTooltip>
<ReactTooltip content="Share content">
<ReactButton
variant="secondary"
aria-label="Share"
>
<Share2
color="var(--ag-neutral-700)"
size={18}
/>
</ReactButton>
</ReactTooltip>
</div>
<div className="mbe4">
<h2>CSS Shadow Parts Customization</h2>
<p className="mbs2 mbe3">
Use CSS Shadow Parts to customize the tooltip's appearance without affecting the component's internal styling. One drawback is that the arrow part can be tricky to style due to its border-based implementation so we hide it in this example.
</p>
</div>
<div className="stacked-mobile mbe4">
<ReactTooltip
className="custom-tooltip-gradient"
content="Customized with CSS Shadow Parts!"
placement="top"
>
<ReactButton variant="primary">Gradient Tooltip</ReactButton>
</ReactTooltip>
</div>
</section>
);
}
Props
| Prop | Type | Default | Description |
|---|---|---|---|
content | string | '' | The text content to display in the tooltip |
placement | 'top' | 'bottom' | 'left' | 'right' | 'top-start' | 'top-end' | 'bottom-start' | 'bottom-end' | 'left-start' | 'left-end' | 'right-start' | 'right-end' | 'top' | Placement of the tooltip relative to the trigger |
distance | number | 8 | Distance in pixels between tooltip and trigger |
skidding | number | 0 | Offset in pixels along the alignment axis |
trigger | string | 'hover focus' | Space-separated trigger events: 'hover', 'focus', 'click' |
disabled | boolean | false | Prevents the tooltip from showing |
Events
| Event | Framework | Detail | Description |
|---|---|---|---|
show | Vue: @showReact: onShowLit: @show or .onShow | { visible: boolean } | Fired when the tooltip becomes visible. The visible property will be true. |
hide | Vue: @hideReact: onHideLit: @hide or .onHide | { visible: boolean } | Fired when the tooltip becomes hidden. The visible property will be false. |
Event Patterns
AgnosticUI Tooltip supports three event handling patterns:
- addEventListener (Lit/Vanilla JS):
const tooltip = document.querySelector('ag-tooltip');
tooltip.addEventListener('show', (e) => {
console.log('Tooltip shown:', e.detail.visible);
});- Callback props (Lit/Vanilla JS):
const tooltip = document.querySelector('ag-tooltip');
tooltip.onShow = (e) => {
console.log('Tooltip shown:', e.detail.visible);
};- Framework event handlers (Vue/React):
<!-- Vue -->
<VueTooltip @show="handleShow" @hide="handleHide" />// React
<ReactTooltip onShow={handleShow} onHide={handleHide} />All three patterns work identically thanks to the dual-dispatch system.
Accessibility
The Tooltip component implements the WAI-ARIA Tooltip Pattern:
- Uses
role="tooltip"for proper screen reader identification - Keyboard support: Focus shows tooltip, Escape dismisses it
- Always include
aria-labelon icon-only buttons - Keep content brief (one sentence or less)
- Include 'focus' trigger for keyboard accessibility
CSS Shadow Parts
| Part | Description |
|---|---|
ag-tooltip | The main tooltip container element that displays the content |
ag-tooltip-arrow | The arrow element that points to the trigger element |
Customization Example
.custom-tooltip::part(ag-tooltip) {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 12px;
padding: 12px 16px;
font-weight: 600;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
max-width: 250px;
}
.custom-tooltip::part(ag-tooltip-arrow) {
background: #667eea;
}<VueTooltip class="custom-tooltip" content="Customized tooltip">
<VueButton>Hover me</VueButton>
</VueTooltip>
<ReactTooltip className="custom-tooltip" content="Customized tooltip">
<ReactButton>Hover me</ReactButton>
</ReactTooltip>
<ag-tooltip class="custom-tooltip" content="Customized tooltip">
<button>Hover me</button>
</ag-tooltip>