Welcome to Vanilla Breeze
This bell pulls live notifications from /go/notify/messages — the same contract documented at /docs/concepts/service-contracts/. Static articles like this one are the no-JS / no-backend fallback.
This bell pulls live notifications from /go/notify/messages — the same contract documented at /docs/concepts/service-contracts/. Static articles like this one are the no-JS / no-backend fallback.
Progressive enhancement tooltips: native title fallback upgraded to styled popovers with arrows and positioning.
Tooltips use a title-first progressive enhancement approach. The title attribute provides a no-JS fallback (native browser tooltip). When JavaScript loads, the init script reads the title, removes it, and creates a styled popover with arrows and positioning.
<!-- No JS: native browser tooltip shows "This is a tooltip!" --><!-- With JS: title removed, styled popover tooltip created --><button title="This is a tooltip!" data-tooltip>Hover me</button>
| State | What the user sees |
|---|---|
| No JavaScript | Native browser tooltip from title attribute |
| JavaScript loaded | Styled popover with arrow, positioned via CSS Anchor or JS fallback |
Add title and data-tooltip (no value) to any element. The init script creates the popover automatically.
Use data-tooltip-position to control where the tooltip appears relative to the trigger.
<button title="Tooltip appears above" data-tooltip>Top</button> <button title="Tooltip appears below" data-tooltip data-tooltip-position="bottom">Bottom</button> <button title="Tooltip appears on the left" data-tooltip data-tooltip-position="left">Left</button> <button title="Tooltip appears on the right" data-tooltip data-tooltip-position="right">Right</button>
Tooltips work on any focusable element: buttons, links, and elements with tabindex.
<!-- On a link --><a href="#" title="Links can have tooltips too!" data-tooltip>Hover this link</a> <!-- On an icon button --><button type="button" class="ghost" aria-label="Settings" title="Open settings" data-tooltip data-tooltip-position="bottom"> <icon-wc name="settings"></icon-wc></button> <!-- On text with tabindex --><span tabindex="0" title="Additional information about this feature" data-tooltip style="text-decoration: underline dotted; cursor: help;"> What's this?</span>
| Attribute | On | Description |
|---|---|---|
title |
trigger | Tooltip text. Read by init script and removed to prevent native double-tooltip. |
data-tooltip |
trigger | Marker for init script discovery. No value = create from title. With value = reference popover by ID. |
data-tooltip-position |
trigger | Position: top (default), bottom, left, or right. |
For tooltips with formatted content (keyboard shortcuts, icons, multiple lines), use data-tooltip="id" to reference an existing popover element. The title provides a plain-text no-JS fallback.
<button type="button" title="Save (Ctrl+S)" data-tooltip="save-tip"> <icon-wc name="save" size="sm"></icon-wc> Save</button><div id="save-tip" popover="hint" role="tooltip"> <strong>Save document</strong><br> <kbd>Ctrl</kbd> + <kbd>S</kbd> <span class="tooltip-arrow" aria-hidden="true"></span></div>
<button type="button" title="Copy to clipboard" data-tooltip="copy-tip"> Copy</button><div id="copy-tip" popover="hint" role="tooltip"> Copy to clipboard<br> <small>Copied items expire after 24 hours</small> <span class="tooltip-arrow" aria-hidden="true"></span></div>
For richer content previews like user profiles or link previews, use <tool-tip variant="card">. Cards use popover="manual" for interactive content and have longer show/hide delays.
<tool-tip variant="card"> <a href="/user/jane" data-trigger>Jane Smith</a> <div data-content> <div style="display: flex; align-items: center; gap: var(--size-s);"> <user-avatar data-size="lg"> <span data-fallback>JS</span> </user-avatar> <div> <strong>Jane Smith</strong> <span class="text-muted" style="display: block;"> Senior Developer </span> </div> </div> <span class="text-muted" style="display: block; margin-block-start: var(--size-xs);"> <icon-wc name="map-pin" size="xs"></icon-wc> San Francisco · 142 contributions </span> </div></tool-tip>
<tool-tip variant="card"> <a href="/docs/elements/native/dialog/" data-trigger>dialog element</a> <div data-content> <div style="display: flex; gap: var(--size-s); align-items: start;"> <icon-wc name="layout-template" size="lg" style="color: var(--color-interactive); flex-shrink: 0;"> </icon-wc> <div> <strong>Dialog Element</strong> <span class="text-muted" style="display: block; margin-block-start: var(--size-2xs);"> Native modal and non-modal dialogs with backdrop, keyboard handling, and focus management. </span> </div> </div> </div></tool-tip>
| Feature | Tooltip (Tier 1/2) | Card variant |
|---|---|---|
| Content model | title or data-tooltip="id" |
[data-trigger] + [data-content] |
| Appearance | Dark, compact, with arrow | Light card surface, border, shadow, no arrow |
| Show delay | 200ms | 300ms |
| Hide delay | 100ms | 200ms |
| Popover type | hint |
manual (supports interactive content) |
| ARIA | role="tooltip", aria-describedby |
None (supplementary content) |
The <tool-tip> component also reads title from its trigger as a content source (lowest priority after <template data-tooltip> and content). This gives progressive enhancement to the web component path too.
<!-- Progressive enhancement: title provides no-JS fallback --><tool-tip> <button type="button" title="Save your changes">Save</button> <template data-tooltip> <strong>Save document</strong><br> <kbd>Ctrl</kbd> + <kbd>S</kbd> </template></tool-tip>
Content priority in tool-tip:
<template data-tooltip> (rich HTML)content attribute (plain text)title on trigger (PE baseline — read and removed)interestfor (Tier 0)For simple tooltips that don't need arrows, custom positioning, or rich content, you can skip the init script entirely. The interestfor attribute (native in Chrome 133+, polyfilled in other browsers) handles hover/focus timing and popover show/hide declaratively.
<!-- Pure HTML tooltip — no init script needed --><button interestfor="wai-def">WAI-ARIA</button><div id="wai-def" popover="hint"> Web Accessibility Initiative – Accessible Rich Internet Applications</div>
Control show/hide delays with CSS custom properties:
/* Custom timing via CSS custom properties */#my-tooltip { --interest-delay-start: 300ms; --interest-delay-end: 150ms;}
| Use case | Approach |
|---|---|
| Simple text hint on a button/link | title + data-tooltip (Tier 1) |
| Rich content (kbd shortcuts, icons) | data-tooltip="id" referencing a popover (Tier 2) |
| Interactive hover card (profiles, previews) | <tool-tip variant="card"> (Tier 3) |
| Zero-JS inline definition | interestfor + popover="hint" (Tier 0) |
Tooltips use the native Popover API with popover="hint". This renders tooltips in the browser's top layer, avoiding z-index conflicts and overflow clipping issues.
In modern browsers (Chrome 125+, Safari 18+), tooltips use CSS Anchor Positioning for smooth, hardware-accelerated positioning. Older browsers fall back to JavaScript positioning automatically.
| Browser | Positioning | Notes |
|---|---|---|
| Chrome 125+ | CSS Anchor | Full support |
| Safari 18+ | CSS Anchor | Full support |
| Firefox | JS Fallback | Anchor positioning not yet supported |
| Older browsers | JS Fallback | Requires Popover API support |
tool-tip)When using <tool-tip>, control how long to wait before showing with delay. Default is 200ms.
<tool-tip delay="0"> <button>Instant</button> <template data-tooltip>Shows immediately</template></tool-tip> <tool-tip delay="500"> <button>Delayed</button> <template data-tooltip>Waits 500ms</template></tool-tip>
tool-tip)| Event | Description |
|---|---|
tool-tip:show |
Fired when the tooltip becomes visible. |
tool-tip:hide |
Fired when the tooltip is hidden. |
const tooltip = document.querySelector('tool-tip'); tooltip.addEventListener('tool-tip:show', () => { console.log('Tooltip shown');}); tooltip.addEventListener('tool-tip:hide', () => { console.log('Tooltip hidden');});
| Method/Property | Type | Description |
|---|---|---|
show() |
method | Show the tooltip immediately. |
hide() |
method | Hide the tooltip immediately. |
isVisible |
property | Read-only boolean indicating if the tooltip is currently visible. |
const tooltip = document.querySelector('tool-tip'); // Show the tooltiptooltip.show(); // Check visibilityconsole.log(tooltip.isVisible); // true // Hide the tooltiptooltip.hide();
Tooltips appear on keyboard focus and can be dismissed with the Escape key. Escape handling is built into the Popover API.
| Key | Action |
|---|---|
| Tab | Focus the trigger element, showing the tooltip |
| Escape | Hide the tooltip while keeping focus on the trigger |
Use Tab to focus the button below, then press Escape to hide the tooltip:
role="tooltip" and popover="hint"aria-describedby pointing to the tooltip (set by init script)aria-hidden="true"When the trigger receives focus, screen readers announce the element followed by the tooltip content (via aria-describedby). This provides context without requiring visual interaction.