# Tooltips

> Material Design 3 tooltips, from the HTML.

## Introduction

A child `.tooltip` is the bubble. No `tooltipped` class is required. They are CSS only on hover and keyboard focus. Helpers are only for placement and the rich variant: `top` (the default), `bottom`, `left`, `right`, `rich` / `max`.

Tokens follow the [M3 tooltip spec](https://m3.material.io/components/tooltips/specs). A plain tooltip is `inverse-surface` / `inverse-on-surface`, 4dp corners, `body-small`, 24dp minimum, 4/8dp padding, 200dp max, no elevation and no caret. It sits 4dp from the activator. M3 prefers above.

Inside a `<button>` the bubble has to be a `<span>` — a `<div>` is not phrasing content and the parser will hoist it. Icon-only buttons still need `.circle` so the span is not treated as a label.

Add to album Above Below Start End

```
<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

`rich` (or BeerCSS’s `max`) is the rich tooltip: `surface`, elevation 2, 12dp corners, 320dp max. A heading is the title (`title-small`), a `<p>` is supporting text (`body-medium`), and a trailing `<nav>` is the action. Rich bubbles can receive pointer events so the action is usable.

Why this is saved

### Saved offline

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

Got it

```
<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 live inside a `<button>` — that would nest buttons. Put the bubble next to the control, wrapped in a parent.

## JavaScript

The CSS path does not need AutoInit. The JS plugin is still there for `data-tooltip`, delayed show/hide, and keeping the bubble inside the viewport. Add `tooltipped` to the activator. `data-tooltip` is the text; `data-position` is `top`, `right`, `bottom`, or `left`. `AutoInit()` starts every `.tooltipped` except those marked `no-autoinit`. The generated element gets both `.tooltip` and `.material-tooltip`.

Bottom Top Left Right

```
<a class="tooltipped" data-position="bottom" data-tooltip="I am a tooltip" href="#!">
  Hover me
</a>
```

For HTML, point `data-tooltip-id` at an element. That element is moved into the tooltip and the bubble is marked `rich`. Leave `data-tooltip` off so the HTML is kept. There is no `data-html` attribute and no `unsafeHTML` option.

With HTML

### Chart

This is a tooltip with a [link](https://github.com) and a .

```
<a class="tooltipped" href="#!"
   data-position="bottom" data-tooltip-id="tooltip-content">
  With HTML
</a>
<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
  });
});
```

Per-instance options can also be passed through AutoInit:

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

## Options

Constructor and `init` options. `data-tooltip`, `data-position`, and `data-tooltip-id` are read from the element and override these defaults. Attributes are read again each time the tooltip opens, so changing them later 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. |
| `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`. M3 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`. The CSS-only default is above. |
| `transitionMovement` | Number | `10` | How far the tooltip moves during its transition, in pixels. |

## Methods

> All methods are called on the plugin instance. You can get the instance like this:

```
const instance = Expressive.Tooltip.getInstance(elem);
```

Hover me Open Close

### .open();

Show the tooltip.

```
instance.open();
```

### .close();

Hide the tooltip.

```
instance.close();
```

### .destroy();

Destroy the plugin instance, remove the tooltip element, and tear down its event handlers.

```
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. |
