Tooltip
Supplemental label for a trigger, shown on hover and focus, hidden on dismissal.
Tooltips are supplemental: data-tooltip on the trigger wires a .tooltip
element (role="tooltip") that appears near the trigger. Position, placement,
and arrow position are written by JavaScript; the element is shown and hidden
by promoting it to the top layer with popover="manual".
- Use
data-tooltipon the trigger. With text (data-tooltip="Help"), the tooltip element is generated. Empty (data-tooltip) marks an explicit tooltip connected viaaria-describedby. - Tooltips are supplemental. Do not put required information or interactive controls inside them.
- Show on hover and focus. Hide on Escape, blur, or pointer leave.
- A trigger scrolled out of view takes its tooltip down and brings it back when it returns; only a dismissal ends the tooltip.
- Escape outlasts the hover and focus that justified the tooltip (WCAG 2.1 SC 1.4.13); showing it again takes a fresh hover or focus, not a second click on a trigger that already has focus.
- Author an explicit tooltip with
hidden. The runtime removes it when it takes the lifecycle over; until then it is what keeps the tooltip off screen. - JavaScript can generate tooltip elements from
data-tooltip. - Add
data-tooltip-clickto toggle it on click instead of hover/focus. - Add
data-tooltip-visibleto show it immediately and keep it visible while its trigger is in view. - Use
data-tooltip-placementto set the preferred placement (defaulttop). - The arrow inherits the tooltip background — custom gradients carry through automatically.
- Avoid relying on the native
titleattribute as the primary implementation.
A .tooltip[popover] may use a native or third-party-owned popover lifecycle.
Actual removes the native inset, margin, and overflow that conflict with
positioning and with the projecting arrow, while leaving bare [popover]
elements untouched. The lifecycle owner still writes position, the
coordinates, and the arrow position; Actual only supplies the presentation.
Do not combine them. tooltip.js writes popover="manual" on every tooltip it
manages and calls showPopover() / hidePopover() itself, so a tooltip handed
to the runtime has its mode overwritten. The runtime only ever reaches a
tooltip that a data-tooltip trigger points at, so a .tooltip[popover] with
no such trigger stays yours to drive. Use data-tooltip and the built-in
runtime when Actual should own showing and hiding.
Where the element lives is part of the contract. An explicit tooltip stays
exactly where you wrote it, so it keeps every scope that reaches it by
inheritance — a theme island, a density scope, .inverted, your own scoped
custom properties. It also means placement is yours to get right: a tooltip
used from inside a modal dialog must be authored inside that dialog, because
the top layer does not lift an element out of a modal's inertness.
A generated tooltip (data-tooltip="Help") is Actual's to place, and it goes
at document level rather than beside the trigger, so it cannot disturb the
trigger's structural position — a generated sibling would take
.join > :last-child from a trigger that ends a group. It therefore makes no
inheritance promise. Use the explicit form when the local CSS context matters.
<button class="btn ghost"
type="button"
data-tooltip
aria-describedby="tooltip-save"
aria-label="Save">
<i class="ti ti-device-floppy" aria-hidden="true"></i>
</button>
<div class="tooltip" role="tooltip" id="tooltip-save" hidden>
Save changes
</div>
<button class="btn" type="button" data-tooltip="Save changes">
Save
</button>
Display modes
Click tooltips stay open when the pointer leaves and close on a second click or Escape. They remain supplemental, non-interactive content; use a flyout when the floating content contains controls.
<p class="cluster">
<button class="btn outline" type="button"
data-tooltip="Click again or press Escape to close"
data-tooltip-click>
Toggle on click
</button>
<button class="btn outline" type="button"
data-tooltip="Always visible while this trigger is in view"
data-tooltip-visible
data-tooltip-placement="bottom">
Always visible
</button>
</p>
HTML and long content
The data-tooltip="…" shorthand is intentionally plain text. For trusted HTML,
use an explicit tooltip referenced by aria-describedby. Keep its content
non-interactive and concise; long content wraps up to the tooltip's
viewport-aware maximum width.
<button class="btn" type="button"
data-tooltip
aria-describedby="tip-rich-long"
data-tooltip-placement="bottom">
Long HTML tooltip
</button>
<div class="tooltip" role="tooltip" id="tip-rich-long" hidden>
<strong>Keyboard shortcut:</strong> press <kbd>Ctrl</kbd> + <kbd>K</kbd> to open search.
This longer explanation demonstrates wrapping when a supplemental label needs a little more context.
</div>
Positioning
Set data-tooltip-placement to control where the tooltip appears relative to
its trigger. The arrow follows the placement automatically.
The runtime also picks the coordinate space, and writes position to match. A
tooltip whose trigger scrolls with the page is placed in document coordinates
and position: absolute, so the browser scrolls it with the page instead of
the positioner correcting it a frame later — the lag visible while scrolling on
a touch device. A trigger that is viewport-anchored — inside a fixed or sticky
ancestor, an open popover, or a modal dialog — keeps position: fixed. Do not
override position on a tooltip the runtime manages. See
Tooltip coordinate space.
<p>
<button class="btn outline" type="button" data-tooltip="Above the button" data-tooltip-placement="top">Top</button>
<button class="btn outline" type="button" data-tooltip="To the right" data-tooltip-placement="right">Right</button>
<button class="btn outline" type="button" data-tooltip="Below the button" data-tooltip-placement="bottom">Bottom</button>
<button class="btn outline" type="button" data-tooltip="To the left" data-tooltip-placement="left">Left</button>
</p>
Custom styling
Override --tooltip-bg and --tooltip-fg on the tooltip element to change the
background and text color. The arrow picks up background: inherit so gradients
and solid colors both work.
<button class="btn primary" type="button"
data-tooltip
aria-describedby="tip-grad"
data-tooltip-placement="right">
Hover for gradient
</button>
<div class="tooltip" role="tooltip" id="tip-grad" hidden
style="background-color: #5b2d9e;
--tooltip-bg: linear-gradient(135deg, oklch(0.45 0.22 280), oklch(0.5 0.2 10));
--tooltip-fg: white;
padding: 0.4em 0.8em;
font-weight: var(--font-weight-medium);">
<strong>Custom gradient tooltip:</strong> the arrow matches the background, and this longer
sentence demonstrates how the tooltip wraps and flips when the preferred side lacks space.
</div>
CSS hooks
--tooltip-bg— bubble background (defaults tovar(--surface-solid)); the arrow follows it.--tooltip-fg— text color.--tooltip-arrow-size— arrow size.
--arrow-x and --arrow-y are written by the positioner at runtime, not set by
the author. See the JavaScript runtime documentation.