# Tooltip

A hover-revealed bubble that displays a brief description or keyboard shortcut when a trigger receives pointer hover or keyboard focus.

```slint
import { Button } from "@glint/components/button.slint";
import { Tooltip } from "@glint/components/tooltip.slint";
import { Tokens } from "@glint/theme/tokens.slint";

export component Demo inherits Window {
    width: 480px;
    height: 180px;
    background: Tokens.color-background;

    HorizontalLayout {
        alignment: center;
        padding: 48px;

        Tooltip {
            text: "Add to favorites";

            Button {
                text: "Bookmark";
            }
        }
    }
}
```

## Usage

```slint
import { Button } from "@glint/components/button.slint";
import { Tooltip, TooltipSide } from "@glint/components/tooltip.slint";

export component AppWindow inherits Window {
    HorizontalLayout {
        alignment: center;

        Tooltip {
            text: "Save document";
            side: TooltipSide.top;

            Button {
                text: "Save";
            }
        }
    }
}
```

A `Tooltip` wraps an interactive trigger control via its `@children` slot and reveals a concise contextual label after a brief hover delay.

Unlike standard toolkit tooltips, Glint’s `Tooltip` renders in a floating `PopupWindow` layer above other elements, sizes itself to its text using design tokens, and supports dual pointer/keyboard activation.

## Examples

### Sides

`side` sets which edge of the trigger the tooltip bubble anchors to: `top` (the default), `right`, `bottom`, or `left`.

```slint
import { Button } from "@glint/components/button.slint";
import { Tooltip, TooltipSide } from "@glint/components/tooltip.slint";
import { Tokens } from "@glint/theme/tokens.slint";

export component Demo inherits Window {
    width: 560px;
    height: 240px;
    background: Tokens.color-background;

    VerticalLayout {
        alignment: center;
        spacing: 24px;
        padding: 32px;

        HorizontalLayout {
            alignment: center;
            spacing: 16px;

            Tooltip {
                text: "Top tooltip";
                side: TooltipSide.top;
                Button { text: "Top"; }
            }

            Tooltip {
                text: "Right tooltip";
                side: TooltipSide.right;
                Button { text: "Right"; }
            }

            Tooltip {
                text: "Bottom tooltip";
                side: TooltipSide.bottom;
                Button { text: "Bottom"; }
            }

            Tooltip {
                text: "Left tooltip";
                side: TooltipSide.left;
                Button { text: "Left"; }
            }
        }
    }
}
```

### Keyboard focus activation

`active` lets you trigger the tooltip from keyboard focus. By binding `active` to the wrapped control’s `focus-held` property, keyboard users navigating with `Tab` receive the same tooltip hints as pointer users.

```slint
import { Button } from "@glint/components/button.slint";
import { Tooltip } from "@glint/components/tooltip.slint";
import { Tokens } from "@glint/theme/tokens.slint";

export component Demo inherits Window {
    width: 480px;
    height: 180px;
    background: Tokens.color-background;

    HorizontalLayout {
        alignment: center;
        padding: 48px;

        Tooltip {
            text: "Press Enter or Space to commit changes";
            active: save-btn.focus-held;

            save-btn := Button {
                text: "Save changes";
            }
        }
    }
}
```

### Custom hover delays

`open-delay` (default 300ms) controls how long the pointer must rest on the trigger before the bubble opens, preventing visual noise during rapid pointer transit. `close-delay` (default 100ms) provides a brief grace period to absorb pointer jitter.

```slint
import { Button } from "@glint/components/button.slint";
import { Tooltip } from "@glint/components/tooltip.slint";
import { Tokens } from "@glint/theme/tokens.slint";

export component Demo inherits Window {
    width: 480px;
    height: 180px;
    background: Tokens.color-background;

    HorizontalLayout {
        alignment: center;
        padding: 48px;

        Tooltip {
            text: "Opens swiftly";
            open-delay: 150ms;
            close-delay: 200ms;

            Button {
                text: "Hover me";
            }
        }
    }
}
```

## API Reference

### Properties

| Property      | Type             | Default           | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| ------------- | ---------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `text`        | `in string`      | no default        | Label rendered inside the bubble.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `side`        | `in TooltipSide` | `TooltipSide.top` | Which edge of the trigger to anchor to — `top` (default), `right`, `bottom`, `left`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `open-delay`  | `in duration`    | `300ms`           | How long the pointer must rest on the trigger before the bubble opens, so incidental hovers never flash it. Radix's `delayDuration` is 700ms; Glint opens sooner, and matches `HoverCard.open-delay` — two surfaces that both appear on hover must not appear at different speeds, or the slower one reads as broken.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `close-delay` | `in duration`    | `100ms`           | How long the bubble outlives a pointer leaving it. Short, because the delay is only there to absorb jitter at the zone edge — long enough and the bubble feels sticky. Published because `HoverCard` publishes it and the two were the same machine with one of them spelling this into a timer.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `active`      | `in bool`        | no default        | A second reason to show the bubble, ORed with the pointer's. shadcn's tooltip is defined as opening on keyboard focus \*or\* hover, so a keyboard user gets what a mouse user gets; bind this to the wrapped control's focus and it does: Tooltip { text: "Save and continue"; active: save.focus-held; save := Button { text: "Save"; } } `focus-held` rather than `focus-visible`, and the difference is the whole of why this property is hard: showing the bubble opens a `PopupWindow`, which takes the window's focus, which clears `focus-visible` on the control below — so a bubble gated on it closed itself the instant it appeared, the focus came back, and it opened again. Measured at 400ms a cycle, with the control's ring blinking in antiphase. `focus-held` is the same focus with the popup's borrowing discounted, so the bubble stops answering a question its own existence makes unanswerable. Tooltip cannot read it off the wrapped control itself: Slint reports focus only to the element that holds it, never to an ancestor, so a component that owns its trigger as `@children` still cannot see the trigger's focus. `HoverCard.active` is the same property for the same reason, and the two hover surfaces now agree. Which is why the controls that own a keyboard focus scope publish `focus-visible` themselves — Button, Toggle, Checkbox, Switch, Slider and Select. Wrapping one in a second `FocusVisibleScope` does not work and is not the shape to reach for: the wrapper reports its \*own\* focus, not the control's, and it adds a tab stop of its own that lands ahead of the control and answers nothing. One limit stays, and it is the pointer's limit on the keyboard's axis: an open `PopupWindow` takes the window's focus as well as its pointer events, so once the bubble is up the control below no longer holds the keyboard. The control answers Enter for the whole `open-delay` before that, and the bubble is a hint rather than a step in the task, so this is the same trade the zone below already documents. It has a second face now that `focus-held` keeps the bubble up: Tab cannot reach the control to report that the focus left, so a bubble the keyboard raised stays until the consumer clears `active` or a click dismisses the popup. That is the trade against a bubble that flashes twice a second, which is what gating on `focus-visible` costs. |
| `is-open`     | `out bool`       | no default        | Whether the bubble is currently shown; mirrors `PopupWindow.is-open` rather than being set by hand, so it cannot disagree with the popup.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |

### Enums

| Enum          | Values                           |
| ------------- | -------------------------------- |
| `TooltipSide` | `top`, `right`, `bottom`, `left` |

## Accessibility

- **Dual activation.** Tooltips support pointer hover and keyboard focus simultaneously when `active` is bound to the trigger’s `focus-held` property.
- **Hover zone & pointer routing.** The popup creates a combined hover zone spanning both the trigger and the bubble, ensuring the bubble does not flicker. A click in that zone dismisses the tooltip and is consumed; the wrapped control responds to the next click.
- **Supported dismissal.** A pointer-opened tooltip closes after the pointer leaves the trigger and hover zone, or immediately when that zone is clicked. `Tooltip` does not handle `Escape` itself.
- **Concise descriptions.** Tooltip text should be reserved for brief hints or shortcuts; for rich or multi-line content, use `HoverCard` or `Popover` instead.
