---
title: Tooltip
description: Hover tooltip with configurable position
visibility: guest
draft: false
---

# Tooltip

The `<x-wirekit::tooltip>` component shows contextual information on hover, focus, and long-press (touch). Positioned via Floating UI with automatic flip/shift.

::: warning
Tooltips should never be the only source of critical information. For essential details, use visible labels or hint text. On touch devices, tooltips are triggered by long-press (500ms).
:::

## Usage

### Text Tooltip

:::preview{title="Text Tooltip"}
<x-wirekit::tooltip text="Save your changes">
    <x-wirekit::button>Save</x-wirekit::button>
</x-wirekit::tooltip>
:::

### Rich Content Tooltip

:::preview{title="Rich Content"}
<x-wirekit::tooltip placement="right">
    <x-slot:content>
        Pro Tip: Use Ctrl+S to save.
    </x-slot:content>
    <x-wirekit::button intent="neutral" surface="ghost" size="sm">Keyboard Shortcuts</x-wirekit::button>
</x-wirekit::tooltip>
:::

## Width & Layout

Plain-text tooltips (`text` prop) auto-size to their content with a max-width for readability. Rich tooltips (slot content) size to their content — add explicit widths if needed:

```blade
<x-wirekit::tooltip>
    <x-slot:content>
        <div class="w-64">Wider tooltip with more detailed content.</div>
    </x-slot:content>
    <x-wirekit::button intent="neutral" surface="ghost" size="sm">Details</x-wirekit::button>
</x-wirekit::tooltip>
```

## Placement

Control where the tooltip appears relative to its trigger:

:::preview{title="Tooltip placements"}
<x-wirekit::row wrap gap="lg" justify="center" class="py-8">
<x-wirekit::tooltip text="Appears above" placement="top">
    <x-wirekit::button intent="neutral" surface="outline">Top</x-wirekit::button>
</x-wirekit::tooltip>
<x-wirekit::tooltip text="Appears below" placement="bottom">
    <x-wirekit::button intent="neutral" surface="outline">Bottom</x-wirekit::button>
</x-wirekit::tooltip>
<x-wirekit::tooltip text="Appears to the left" placement="left">
    <x-wirekit::button intent="neutral" surface="outline">Left</x-wirekit::button>
</x-wirekit::tooltip>
<x-wirekit::tooltip text="Appears to the right" placement="right">
    <x-wirekit::button intent="neutral" surface="outline">Right</x-wirekit::button>
</x-wirekit::tooltip>
</x-wirekit::row>
:::

Available placements: `top`, `top-start`, `top-end`, `bottom`, `bottom-start`, `bottom-end`, `left`, `left-start`, `left-end`, `right`, `right-start`, `right-end`.

## Styling Variants

The tooltip's surface, text color, border radius, and opacity all derive from CSS variables — override them per-tooltip via inline `style` (scopes to the trigger wrapper) or globally via theme `:root`. Hover each button below to see a different style applied without changing the component itself.

:::preview{title="Tooltip styling variants — hover each button"}
<x-wirekit::row wrap gap="lg" justify="center" class="py-8">
<x-wirekit::tooltip text="Default — accent text on inverted surface">
    <x-wirekit::button intent="neutral" surface="outline">Default</x-wirekit::button>
</x-wirekit::tooltip>
<x-wirekit::tooltip text="Success — green tint" style="--color-wk-tooltip-bg: var(--color-wk-success); --color-wk-tooltip-text: var(--color-wk-success-fg);">
    <x-wirekit::button intent="neutral" surface="outline">Success</x-wirekit::button>
</x-wirekit::tooltip>
<x-wirekit::tooltip text="Warning — amber tint" style="--color-wk-tooltip-bg: var(--color-wk-warning); --color-wk-tooltip-text: var(--color-wk-warning-fg);">
    <x-wirekit::button intent="neutral" surface="outline">Warning</x-wirekit::button>
</x-wirekit::tooltip>
<x-wirekit::tooltip text="Danger — red tint" style="--color-wk-tooltip-bg: var(--color-wk-danger); --color-wk-tooltip-text: var(--color-wk-danger-fg);">
    <x-wirekit::button intent="neutral" surface="outline">Danger</x-wirekit::button>
</x-wirekit::tooltip>
<x-wirekit::tooltip text="Translucent — see the trigger through the surface" style="--color-wk-tooltip-bg: color-mix(in srgb, var(--color-wk-text) 80%, transparent); --color-wk-tooltip-text: var(--color-wk-bg);">
    <x-wirekit::button intent="neutral" surface="outline">Translucent</x-wirekit::button>
</x-wirekit::tooltip>
<x-wirekit::tooltip text="Inverted — light surface on dark themes, dark on light" style="--color-wk-tooltip-bg: var(--color-wk-bg-elevated); --color-wk-tooltip-text: var(--color-wk-text);">
    <x-wirekit::button intent="neutral" surface="outline">Inverted</x-wirekit::button>
</x-wirekit::tooltip>
</x-wirekit::row>
:::

Per-tooltip overrides win because the inline `style` cascades onto the hover-trigger wrapper, which the tooltip panel reads via `var(--color-wk-tooltip-*)`. For an app-wide tooltip palette change, set the same tokens on `:root {}` in `app.css` instead of per-instance.

## Custom Delays

Control how quickly tooltips appear and disappear. In the preview below, the left button shows instantly and hides slowly; the right button waits before showing and hides quickly:

:::preview{title="Custom delays"}
<x-wirekit::row gap="md" justify="center" wrap>
<x-wirekit::tooltip text="Shows fast, hides slow" :delay-show="0" :delay-hide="800">
    <x-wirekit::button intent="neutral" surface="outline">Fast show, slow hide</x-wirekit::button>
</x-wirekit::tooltip>
<x-wirekit::tooltip text="Shows slow, hides fast" :delay-show="800" :delay-hide="0">
    <x-wirekit::button intent="neutral" surface="outline">Slow show, fast hide</x-wirekit::button>
</x-wirekit::tooltip>
</x-wirekit::row>
:::

## Trigger Behavior

| Device | Show Trigger | Hide Trigger | Auto-Dismiss |
| --- | --- | --- | --- |
| Mouse (desktop) | `mouseenter` + delay | `mouseleave` + delay | No |
| Keyboard | `focusin` | `focusout` | No |
| Touch | Long-press (500ms) | Finger release | Yes, after 1500ms |

## Keyboard

| Key | Action |
| --- | --- |
| Escape | Hide tooltip immediately |

## Theming

Tooltips use inverted colors — dark background on light mode, light background on dark mode:

| Variable | Usage |
| --- | --- |
| `--color-wk-tooltip-bg` | Tooltip background (defaults to text color) |
| `--color-wk-tooltip-text` | Tooltip text (defaults to background color) |
| `--size-wk-tooltip-max` | Maximum width (default: 20rem) |
| `--z-wk-tooltip` | Z-index layer (default: 60) |

## Behavior

- **[Floating UI](https://floating-ui.com/)** (bundled ~3.5 KB) positions the tooltip with automatic [flip](https://floating-ui.com/docs/flip) and [shift](https://floating-ui.com/docs/shift)
- **Transitions** use opacity fade for smooth show/hide
- **Livewire SPA** navigation closes open tooltips
- **Touch devices** use long-press (500ms) to trigger, auto-dismiss after 1.5s

## Switching a Tooltip Off

A tooltip on a control that has become inert should go quiet. `disabled` does that:

:::preview{title="A tooltip that is switched off"}
<x-wirekit::row gap="md" align="center">
    <x-wirekit::tooltip text="You will see this one">
        <x-wirekit::button intent="neutral" surface="outline">Hover me</x-wirekit::button>
    </x-wirekit::tooltip>
    <x-wirekit::tooltip text="You will not see this one" :disabled="true">
        <x-wirekit::button intent="neutral" surface="outline" disabled>Unavailable</x-wirekit::button>
    </x-wirekit::tooltip>
</x-wirekit::row>
:::

**`pointer-events: none` on the wrapper is not a way to do this**, however much it looks like one. `mouseenter` is delivered to every ancestor of the element the pointer actually hit, whatever those ancestors' own `pointer-events` value — so the handler still fires and the panel still appears, over a control that is supposed to be dead.

### Following live state

The prop answers for the render that produced it. When the answer changes while the page is open — a sidebar collapses, a row enters a loading state — bind the attribute the component reads instead. No extra prop is involved; it is the same switch, read at the moment the tooltip would open:

```blade
{{-- 1. `quiet` is your own Alpine state — anything reactive works. --}}
<div x-data="{ quiet: true }">
    {{-- 2. The component reads this attribute when a hover or focus would open
            the panel, so it follows the value rather than a snapshot of it. --}}
    <x-wirekit::tooltip text="Only while the row is idle"
        x-bind:data-wk-tooltip-disabled="quiet ? 'true' : 'false'">
        <x-wirekit::button intent="neutral" surface="outline">Retry</x-wirekit::button>
    </x-wirekit::tooltip>

    {{-- 3. Nothing re-renders and nothing re-initializes when this flips. --}}
    <x-wirekit::button x-on:click="quiet = false">Allow it</x-wirekit::button>
</div>
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `text` | `string\|null` | `null` | Plain-text tooltip content |
| `placement` | `string` | `'top'` | [Floating UI placement](https://floating-ui.com/docs/computePosition#placement) |
| `offset` | `int` | `6` | Distance in px between trigger and tooltip |
| `delay-show` | `int` | `300` | Delay in ms before showing (desktop hover) |
| `delay-hide` | `int` | `100` | Delay in ms before hiding (desktop mouseleave) |
| `focusableTrigger` | `bool` | `true` | Make the trigger keyboard-focusable (WCAG 2.1.1) so the tooltip shows on focus, not just hover. Set `false` when the slot is already interactive (a button/link) to avoid a double tab-stop. |
| `disabled` | `bool` | `false` | Switch the tooltip off without removing it. See below for the live form. |
| `scope` | `string\|null` | `null` | Scoped personalization key |

## Slots

| Slot | Purpose |
| --- | --- |
| default | The trigger element (hovered/focused to show tooltip) |
| `content` | Rich HTML tooltip content (alternative to `text` prop) |

## Accessibility

- Trigger: `aria-describedby="{tooltipId}"`
- Tooltip: `role="tooltip"`, matching `id`
- Unique ID generated per tooltip instance
- `pointer-events: none` on tooltip panel (prevents interaction interference)

## Keyboard Interaction

| Key | Action |
|-----|--------|
| `Tab` (focusing the trigger) | Show the tooltip |
| `Tab` (away from trigger) | Hide the tooltip |
| `Escape` | Hide the tooltip while the trigger keeps focus |

## Pitfalls

- **Don't put interactive content inside a tooltip.** Tooltips are descriptive — buttons, links, and form controls inside them are not reachable by keyboard. Use `<x-wirekit::popover>` instead.
- **Don't use a tooltip as the primary label.** Touch users have no hover, and screen readers don't announce tooltip content reliably across browsers. The visible label MUST stand on its own.

## Design Tokens

| Token | Used for |
| --- | --- |
| `--font-wk-sans` | Tooltip font family |
| `--text-wk-sm` | Tooltip font size |
| `--color-wk-tooltip-bg` | Tooltip background |
| `--color-wk-tooltip-text` | Tooltip text |
| `--radius-wk-sm` | Border radius |
| `--shadow-wk-md` | Drop shadow |
| `--size-wk-tooltip-max` | Maximum width |
| `--padding-wk-x-sm` / `--padding-wk-y-xs` | Padding |
| `--z-wk-tooltip` | Stacking context |

## Usage & Conventions

::: info
**Need a click-triggered hint?** A tooltip is hover/focus only by design (the WAI-ARIA tooltip pattern). For a "click the **?** icon to reveal a note" affordance — or any content with buttons, links, or form fields — use [Popover](/components/popover) (click-toggled, full focus management). For rich content that previews on hover, use [Hover Card](/components/hover-card).
:::

## Further Reading

- [WAI-ARIA Tooltip Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/tooltip/) — the authoring pattern this component implements
- [MDN: `role="tooltip"`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/tooltip_role)
- [Floating UI](https://floating-ui.com/) — positioning engine (bundled, ~3.5 KB)
- [Inclusive Components: Tooltips & Toggletips](https://inclusive-components.design/tooltips-toggletips/)
