Skip to content

ExpressiveCSS

Browse documentation

Tooltips

Material Design 3 tooltips, from the HTML.

View as Markdown

Put a .tooltip child inside the activator to make the bubble. The activator does not need a tooltipped class. Tooltips show on hover and keyboard focus with CSS alone. Helper classes set only placement and the rich variant: top (the default), bottom, left, right, and rich.

The hover area of a plain tooltip includes the gap to the activator. Runtime tooltips support Escape dismissal and reduced motion.

On touch, a runtime tooltip shows after a half-second long press, without enterDelay. It stays for a second and a half after the finger lifts. The long press does not press the button or follow the link. A tap, or a press that turns into a scroll, shows nothing. With openOnClick, a tap counts as the click. It opens and closes the tooltip, and the long press is off.

A runtime tooltip skips its enterDelay if it opens while another tooltip is closing, or within 300 ms after that one's exitDelay ends. Moving along a toolbar therefore shows each tooltip at once.

CSS-only tooltips have no Escape handler. Use them only after you verify the applicable WCAG 1.4.13 conditions and dismissal exception. Otherwise, use a verified runtime tooltip or persistent supporting text.

Tokens follow the M3 tooltip spec. A plain tooltip uses inverse-surface for the container and inverse-on-surface for the text, with 4dp corners and body-small type. It is at least 24dp tall and at most 200dp wide, with 4dp vertical and 8dp horizontal padding. It has no elevation or caret. It sits 4dp from the activator, and M3 recommends placing it above.

Inside a <button>, make the bubble a <span>. A <div> is not phrasing content, so the parser hoists it out. Icon-only buttons still need .circle so the span is not treated as a label.

<button type="button" class="circle" aria-label="Add" aria-describedby="tip-add-to-album">
  <span class="material-symbols" aria-hidden="true">add</span>
  <span class="tooltip" id="tip-add-to-album">Add to album</span>
</button>

<button type="button" class="circle" aria-label="Below" aria-describedby="tip-below">
  <span class="material-symbols" aria-hidden="true">arrow_downward</span>
  <span class="tooltip bottom" id="tip-below">Below</span>
</button>

Rich

Add rich for a rich tooltip with a surface container, elevation 2, 12dp corners, and a 320dp maximum width. Inside it, a heading is the title (title-small), a <p> is supporting text (body-medium), and a trailing <nav> holds the action. Rich bubbles receive pointer events, so the user can click the action.

Saved offline

This stop is stored on the device so it still opens without a signal.

<div>
  <button type="button" class="tonal">Why this is saved</button>
  <div class="tooltip rich bottom">
    <h3>Saved offline</h3>
    <p>This stop is stored on the device so it still opens without a signal.</p>
    <nav>
      <button type="button" class="text">Got it</button>
    </nav>
  </div>
</div>

A rich tooltip with an action cannot go inside a <button>, because that would nest buttons. Put the bubble next to the control and wrap both in a parent.

JavaScript

To use the JS plugin, add tooltipped to the activator. data-tooltip holds the text, and data-position is top, right, bottom, or left. The plugin handles data-tooltip, delayed show and hide, and keeping the bubble inside the viewport. CSS-only tooltips do not need AutoInit. AutoInit() starts every .tooltipped except those marked no-autoinit. The generated element gets .tooltip (plus .rich when tooltipId is set).

In browsers with CSS anchor positioning, the plugin anchors the bubble to its activator instead of measuring. The bubble stays beside the activator while the page or a container scrolls, and flips to the opposite side at the viewport edge. An anchored tooltip fades in without sliding, because script cannot tell which side it flipped to. The plugin adds transitionMovement to the gap instead. Other browsers keep the measured, viewport-clamped position.

<button type="button" class="tooltipped" data-position="bottom" data-tooltip="I am a tooltip">
  Hover me
</button>

For HTML content, point data-tooltip-id at an element. The plugin moves that element into the tooltip and marks the bubble rich. Leave data-tooltip off so the plugin keeps the HTML. There is no data-html attribute and no unsafeHTML option.

<button type="button" class="tooltipped"
        data-position="bottom" data-tooltip-id="tooltip-content">
  With HTML
</button>
<div id="tooltip-content" hidden>
  <h3>Chart</h3>
  <p>This is a tooltip with a <a href="https://github.com">link</a>.</p>
</div>

Initialization

The IIFE bundle exposes Expressive.Tooltip. Call init yourself when you need options other than the defaults, or let Expressive.AutoInit() start every .tooltipped.

document.addEventListener('DOMContentLoaded', function() {
  const elems = document.querySelectorAll('.tooltipped');
  const instances = Expressive.Tooltip.init(elems, {
    enterDelay: 200
  });
});

You can also pass per-instance options through AutoInit:

Expressive.AutoInit(document.body, {
  Tooltip: { enterDelay: 200 }
});

Options

Pass these to the constructor or init. data-tooltip, data-position, and data-tooltip-id on the element override these defaults. The plugin reads the attributes again each time the tooltip opens, so a later change takes effect on the next show.

Name Type Default Description
exitDelay Number 200 Delay before the tooltip disappears, in milliseconds.
enterDelay Number 0 Delay before the tooltip appears, in milliseconds. Skipped while a tooltip is closing and for 300 ms after its exitDelay ends.
tooltipId String — Id of an element used as the tooltip body. Set by data-tooltip-id. Marks the bubble rich.
text String '' Plain-text content. Set by data-tooltip. Ignored when tooltipId is set.
margin Number 4 Distance from the activator, in pixels, not counting transitionMovement. The M3 value is 4dp.
inDuration Number 250 Enter transition duration, in milliseconds.
opacity Number 1 Opacity of the tooltip when shown.
outDuration Number 200 Exit transition duration, in milliseconds.
position String 'bottom' Direction: 'top', 'right', 'bottom', or 'left'. Set by data-position. CSS-only tooltips default to above.
transitionMovement Number 10 How far the tooltip moves during its transition, in pixels.
openOnClick Boolean false Open on a click instead of hover and focus, at once, without enterDelay. A second click, Escape, a click elsewhere or focus moving to another element closes it at once. A click inside the tooltip does not. Keyboard users open it with Enter or Space, so make the trigger a <button>.

Methods

All methods are called on the plugin instance. You can get the instance like this:
const instance = Expressive.Tooltip.getInstance(elem);

.open();

open() and open(true) show the tooltip after its enter delay, with no hover or focus needed. open(false) uses the automatic behavior, so the activator must be hovered or focused when the delay ends. With openOnClick, a click elsewhere also closes a tooltip opened this way.

instance.open();
.close();

Hide the tooltip.

instance.close();
.destroy();

Destroy the plugin instance and remove its generated tooltip and event handlers. Rich content returns to its authored position and presentation state. The method keeps existing description associations, so you can initialize the same content again.

instance.destroy();

Properties

Name Type Description
el Element The DOM element the plugin was initialized with.
options Object The options the instance was initialized with.
isOpen Boolean Whether the tooltip is open.
isHovered Boolean Whether the activator is hovered.
isFocused Boolean Whether the activator is focused via the keyboard.
tooltipEl Element The generated tooltip element.

No matching pages

  • Source color

    The seed every generated ramp derives from. Pick one and browse the docs — the whole theme follows. Error does not: it is a fixed hue.

    Default source: Mist Iris.

    ExpressiveCSS presets: Lagoon, Harbor, Breeze, Cobalt, Iris.

    Bold

    Soft

    Mist

    Glow