# Tooltip

> A small help text shown on hover and focus. CSS component with two classes and no JavaScript.

A bubble with an addition to something that is already on the screen. It must never carry information the user needs: on a touch screen there is neither hover nor focus until something is tapped.

If the text is needed to fill in a field, it belongs in a [Help Text](/components/help-text.md) under the field.

## How to use it

The bubble is hidden until the trigger gets the mouse or focus. Hold the mouse over the button below, or move to it with Tab, and it appears:

**Plain HTML**

```html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@lo-tek/interfaces@0.36.0/src/tokens/tokens.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@lo-tek/interfaces@0.36.0/src/components/css/tooltip/tooltip.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@lo-tek/interfaces@0.36.0/src/components/css/button/button.css">
<!-- Or everything in one file: https://cdn.jsdelivr.net/npm/@lo-tek/interfaces@0.36.0/dist/lotek.css -->
```

```html
<span class="lt-tooltip">
<button class="lt-button" data-variant="ghost" type="button" aria-describedby="archive-hint">
  Archive
</button>
<span class="lt-tooltip__bubble" role="tooltip" id="archive-hint">
  The case is moved to the archive and can be brought back
</span>
</span>
```

**TypeScript**

```ts
import "@lo-tek/interfaces/tokens.css"
import "@lo-tek/interfaces/tooltip.css"
import "@lo-tek/interfaces/button.css"
```

`lt` is used in two ways, and which one depends on whether you create the element or find it.

**If you create the markup,** you spread the attributes in. That is the route in React, Astro and anything else that renders:

```tsx
import { lt } from "@lo-tek/interfaces/react"


<span {...lt.tooltip()}>
  <button {...lt.button({ variant: "ghost" })} type="button" aria-describedby="help">?</button>
  <span className={lt.tooltip.bubble} id="help" role="tooltip">We never send spam.</span>
</span>
```

The import goes to `/react` here, since the block is JSX. From there the keys are called `className` and `htmlFor`. In Astro and anything else without React you import `@lo-tek/interfaces` and get `class` and `for`.

**If the element is already there,** you set them on it. That is the route when you enhance HTML a server or a template has sent:

```ts
import { lt } from "@lo-tek/interfaces"


const wrapper = document.querySelector<HTMLElement>("#archive")


lt.setAttributes(wrapper, lt.tooltip())
```

`lt.tooltip.bubble` gives the class for the bubble. See [Type-safe use](/type-safe-use.md).

Two things must be right:

| What | Why |
| - | - |
| `aria-describedby` from the trigger to the bubble | Without it the bubble is just something that appears on the screen, and is never announced |
| `role="tooltip"` on the bubble | Says what kind of text it is |

The bubble is shown on `:hover` and on `:focus-within`. The focus part is not optional: a bubble that only appears with the mouse does not exist for someone using the keyboard.

## Icon button with a name

A button with only an icon needs a name, not a help bubble. The two are different things:

```html
<span class="lt-tooltip">
  <button class="lt-button" data-variant="ghost" type="button"
    aria-label="Archive the case" aria-describedby="archive-hint">
    <svg aria-hidden="true" width="20" height="20">…</svg>
  </button>
  <span class="lt-tooltip__bubble" role="tooltip" id="archive-hint">
    The case can be brought back later
  </span>
</span>
```

`aria-label` is the name the button is announced with. The bubble is the addition. If you use the bubble as the name, the button has no name until it gets focus.

## Classes you can style

| Class | What it is |
| - | - |
| `.lt-tooltip` | The wrapper around the trigger and the bubble |
| `.lt-tooltip__bubble` | The bubble with the text |

| Variable | Default |
| - | - |
| `--lt-tooltip-width` | `16rem` |
| `--lt-tooltip-padding` | `var(--lt-spacing-1) var(--lt-spacing-2)` |
| `--lt-tooltip-radius` | `var(--lt-spacing-1)` |

The bubble sits above the trigger. If the trigger is at the very top of the page, you have to move the bubble down yourself:

```css
.lt-tooltip__bubble {
  inset-block-end: auto;
  inset-block-start: calc(100% + var(--lt-spacing-2));
}


/* The bridge across the gap must move down too, otherwise the bubble disappears on the way to it. */
.lt-tooltip:is(:hover, :focus-within)::before {
  inset-block-end: auto;
  inset-block-start: 100%;
}
```

The bubble is `display: none` when hidden, so it takes no space, and fades in with `opacity`. A hidden bubble is out of the accessibility tree, but `aria-describedby` reads the text it points to even when it is hidden, so the description follows the button before the bubble is shown.

See [Customising](/customising.md) for how the variables and the layer work.

## Accessibility

- Nothing that exists only in the bubble can be necessary. Assume half the users never see it.
- Keep the text short. The bubble cannot scroll. The mouse can move from the button up onto the bubble without it disappearing, but it closes when the mouse leaves both.
- Do not put links or buttons inside. The bubble is a description, not a panel.
- **The bubble cannot be dismissed with Escape.** WCAG 1.4.13 requires that content appearing on hover and focus can be dismissed without moving the mouse or focus. That requires JavaScript, and Tooltip is only CSS. So use it only where the bubble does not cover anything the user needs to see, or use [Popover](/components/popover.md), which closes with Escape.
- **The bubble is not moved in from the edge of the screen.** It sits centred above the button. If the button is right at the left or right edge on a narrow screen, part of the bubble ends up outside. Moving it requires anchor positioning in CSS, which is not in all browsers yet, or JavaScript. So do not put a button with a bubble right at the edge.
- A bubble on a field that already has help text is doubling up. Choose one.
