---
name: tooltip
title: Tooltip
url: https://ui.frontic.com/components/ui/tooltip.md
tier: ui
install: npx @frontic/ui add tooltip
files:
  - components/ui/tooltip/Tooltip.vue
  - components/ui/tooltip/TooltipContent.vue
  - components/ui/tooltip/TooltipProvider.vue
  - components/ui/tooltip/TooltipTrigger.vue
  - components/ui/tooltip/index.ts
dependencies:
  - "@vueuse/core"
  - reka-ui
---

# Tooltip

A short label that appears beside an element on hover or keyboard focus, such as naming an icon button; announced as the trigger's description.

## Install

```sh
npx @frontic/ui add tooltip
```

Used by: [Sidebar](https://ui.frontic.com/components/ui/sidebar.md), [Order Tracking](https://ui.frontic.com/components/sections/order-tracking.md), [Checkout Page](https://ui.frontic.com/components/templates/checkout-page.md)

npm packages: `@vueuse/core`, `reka-ui`

## Examples

```vue
<script setup lang="ts">
import { HeartIcon } from '@lucide/vue'

import { Button } from '@/components/ui/button'
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
</script>

<template>
  <TooltipProvider>
    <Tooltip default-open>
      <TooltipTrigger as-child
        ><Button variant="outline" color="neutral" size="icon-md" aria-label="Save to wishlist"><HeartIcon data-icon /></Button
      ></TooltipTrigger>
      <TooltipContent>Save to wishlist</TooltipContent>
    </Tooltip>
  </TooltipProvider>
</template>
```

The example also uses `button`, which `add tooltip` leaves out: `npx @frontic/ui add button`.

## Anatomy

```vue
<TooltipProvider>
  <Tooltip>
    <TooltipTrigger />
    <TooltipContent />
  </Tooltip>
</TooltipProvider>
```

## API reference

### Tooltip

Groups a trigger with the short hint shown when the user hovers or focuses it.

`data-slot="tooltip"` · `Tooltip.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `defaultOpen` | `boolean` | `false` | The open state of the tooltip when it is initially rendered. Use when you do not need to control its open state. |
| `open` | `boolean` |  | The controlled open state of the tooltip. |
| `delayDuration` | `number` | `700` | Override the duration given to the `Provider` to customise the open delay for a specific tooltip. |
| `disableHoverableContent` | `boolean` |  | Prevents Tooltip.Content from remaining open when hovering. Disabling this has accessibility consequences. Inherits from Tooltip.Provider. |
| `disableClosingTrigger` | `boolean` | `false` | When `true`, clicking on trigger will not close the content. |
| `disabled` | `boolean` | `false` | When `true`, disable tooltip |
| `ignoreNonKeyboardFocus` | `boolean` | `false` | Prevent the tooltip from opening if the focus did not come from the keyboard by matching against the `:focus-visible` selector. This is useful if you want to avoid opening it when switching browser tabs or closing a dialog. |

| Event | Payload | Description |
| --- | --- | --- |
| `update:open` | `value: boolean` | Event handler called when the open state of the tooltip changes. |

| Slot | Slot props | Description |
| --- | --- | --- |
| `default` | `{ open: boolean; }` | Holds the trigger and the tooltip content; gets whether the tooltip is open. |

### TooltipContent

Holds the short hint the tooltip shows.

`data-slot="tooltip-content"` · `TooltipContent.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `forceMount` | `boolean` | `false` | Used to force mounting when more control is needed. Useful when controlling animation with Vue animation libraries. |
| `ariaLabel` | `string` | `String` | By default, screenreaders will announce the content inside the component. If this is not descriptive enough, or you have content that cannot be announced, use aria-label as a more descriptive label. |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` |  | The element or component this component should render as. Can be overwritten by `asChild`. |
| `side` | `'top' \| 'right' \| 'bottom' \| 'left'` | `'bottom'` | The preferred side of the trigger to render against when open. Will be reversed when collisions occur and avoidCollisions is enabled. |
| `sideOffset` | `number` | `0` | The distance in pixels from the trigger. |
| `align` | `'center' \| 'start' \| 'end'` | `'center'` | The preferred alignment against the trigger. May change when collisions occur. |
| `alignOffset` | `number` | `0` | An offset in pixels from the `start` or `end` alignment options. |
| `avoidCollisions` | `boolean` | `true` | When `true`, overrides the side and align preferences to prevent collisions with boundary edges. |
| `collisionBoundary` | `Element \| (Element \| null)[] \| null`: `null \| Element \| (Element \| null)[]` | `[]` | The element used as the collision boundary. By default this is the viewport, though you can provide additional element(s) to be included in this check. |
| `collisionPadding` | `number \| Partial<Record<'top' \| 'right' \| 'bottom' \| 'left', number>>` | `0` | The distance in pixels from the boundary edges where collision detection should occur. Accepts a number (same for all sides), or a partial padding object, for example: { top: 20, left: 20 }. |
| `arrowPadding` | `number` | `0` | The padding between the arrow and the edges of the content. If your content has border-radius, this will prevent it from overflowing the corners. |
| `sticky` | `'partial' \| 'always'` | `'partial'` | The sticky behavior on the align axis. `partial` will keep the content in the boundary as long as the trigger is at least partially in the boundary whilst "always" will keep the content in the boundary regardless. |
| `hideWhenDetached` | `boolean` | `false` | Whether to hide the content when the trigger becomes fully occluded. |
| `positionStrategy` | `'absolute' \| 'fixed'` |  | The type of CSS position property to use. |
| `updatePositionStrategy` | `'always' \| 'optimized'` | `'optimized'` | Strategy to update the position of the floating element on every animation frame. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

| Event | Payload | Description |
| --- | --- | --- |
| `escapeKeyDown` | `event: KeyboardEvent` | Event handler called when focus moves to the destructive action after opening. It can be prevented by calling `event.preventDefault` |
| `pointerDownOutside` | `event: Event` | Event handler called when a pointer event occurs outside the bounds of the component. It can be prevented by calling `event.preventDefault`. |

### TooltipProvider

Shares timing settings between all tooltips inside it.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `delayDuration` | `number` | `0` | The duration from when the pointer enters the trigger until the tooltip gets opened. |
| `skipDelayDuration` | `number` | `300` | How much time a user has to enter another trigger without incurring a delay again. |
| `disableHoverableContent` | `boolean` | `false` | When `true`, trying to hover the content will result in the tooltip closing as the pointer leaves the trigger. |
| `disableClosingTrigger` | `boolean` | `false` | When `true`, clicking on trigger will not close the content. |
| `disabled` | `boolean` | `false` | When `true`, disable tooltip |
| `ignoreNonKeyboardFocus` | `boolean` | `false` | Prevent the tooltip from opening if the focus did not come from the keyboard by matching against the `:focus-visible` selector. This is useful if you want to avoid opening it when switching browser tabs or closing a dialog. |
| `content` | `TooltipContentProps` |  | Default settings that will be used by all tooltip components. |

### TooltipTrigger

Marks the element that shows the tooltip on hover or focus. Renders a `<button>`.

`data-slot="tooltip-trigger"` · `TooltipTrigger.vue`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `reference` | `ReferenceElement`: `Element \| VirtualElement` |  | The reference (or anchor) element that is being referred to for positioning.<br><br>If not provided will use the current component as anchor. |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'button'` | The element or component this component should render as. Can be overwritten by `asChild`. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-grace-area-trigger` |  | Marks the trigger, so the pointer can cross to the content without closing it. |
| `data-state` | `'closed' \| 'delayed-open' \| 'instant-open'` | The state the part is in, one value per state. |

## Styling

| Part | Recipe |
| --- | --- |
| `content` | [`tooltip-content`](https://ui.frontic.com/DESIGN.md#tooltip-content) |
| `arrow` | [`tooltip-arrow`](https://ui.frontic.com/DESIGN.md#tooltip-arrow) |

## Source

`components/ui/tooltip/Tooltip.vue`

```vue
<script lang="ts">
/** Groups a trigger with the short hint shown when the user hovers or focuses it. */
export default {}
</script>

<script setup lang="ts">
import type { TooltipRootEmits, TooltipRootProps } from 'reka-ui'
import { TooltipRoot, useForwardPropsEmits } from 'reka-ui'

const props = defineProps<TooltipRootProps>()
const emits = defineEmits<TooltipRootEmits>()

const forwarded = useForwardPropsEmits(props, emits)

defineSlots<{
  /** Holds the trigger and the tooltip content; gets whether the tooltip is open. */
  default?: (props: { open: boolean }) => unknown
}>()
</script>

<template>
  <TooltipRoot v-slot="slotProps" data-slot="tooltip" v-bind="forwarded">
    <slot v-bind="slotProps" />
  </TooltipRoot>
</template>
```

`components/ui/tooltip/TooltipContent.vue`

```vue
<script lang="ts">
/** Holds the short hint the tooltip shows. */
export default {}
</script>

<script setup lang="ts">
import type { TooltipContentEmits, TooltipContentProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { TooltipArrow, TooltipContent, TooltipPortal, useForwardPropsEmits } from 'reka-ui'

defineOptions({
  inheritAttrs: false,
})

const props = withDefaults(defineProps<TooltipContentProps & { class?: HTMLAttributes['class'] }>(), {
  sideOffset: 0,
})

const emits = defineEmits<TooltipContentEmits>()

const delegatedProps = reactiveOmit(props, 'class')
const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script>

<template>
  <TooltipPortal>
    <TooltipContent
      data-slot="tooltip-content"
      v-bind="{ ...forwarded, ...$attrs }"
      :class="[
        'fui-tooltip-content has-[[data-slot=kbd]]:pe-1.5 [&_[data-slot=kbd]]:relative [&_[data-slot=kbd]]:isolate [&_[data-slot=kbd]]:z-50 z-50 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-state-delayed-open:animate-in data-state-delayed-open:fade-in-0 data-state-delayed-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 data-side-bottom:slide-in-from-top-2 data-side-left:slide-in-from-right-2 data-side-right:slide-in-from-left-2 data-side-top:slide-in-from-bottom-2 origin-(--reka-tooltip-content-transform-origin)',
        props.class,
      ]"
    >
      <slot />

      <TooltipArrow aria-hidden="true" class="fui-tooltip-arrow z-50 -translate-y-1/2 rotate-45" />
    </TooltipContent>
  </TooltipPortal>
</template>
```

`components/ui/tooltip/TooltipProvider.vue`

```vue
<script lang="ts">
/** Shares timing settings between all tooltips inside it. */
export default {}
</script>

<script setup lang="ts">
import type { TooltipProviderProps } from 'reka-ui'
import { TooltipProvider, useForwardProps } from 'reka-ui'

const props = withDefaults(defineProps<TooltipProviderProps>(), {
  delayDuration: 0,
})
const forwarded = useForwardProps(props)
</script>

<template>
  <TooltipProvider v-bind="forwarded">
    <slot />
  </TooltipProvider>
</template>
```

`components/ui/tooltip/TooltipTrigger.vue`

```vue
<script lang="ts">
/** Marks the element that shows the tooltip on hover or focus. */
export default {}
</script>

<script setup lang="ts">
import type { TooltipTriggerProps } from 'reka-ui'
import { TooltipTrigger, useForwardProps } from 'reka-ui'

const props = defineProps<TooltipTriggerProps>()
const forwarded = useForwardProps(props)
</script>

<template>
  <TooltipTrigger data-slot="tooltip-trigger" v-bind="forwarded">
    <slot />
  </TooltipTrigger>
</template>
```

## API of the items the example uses

### Button

[Full docs](https://ui.frontic.com/components/ui/button.md) · `npx @frontic/ui add button`

#### Button

Lets the user trigger an action, such as adding a product to the cart. Renders a `<button>`.

`data-slot="button"` · `Button.vue`

Styling: `button`, which extends [`action`](https://ui.frontic.com/DESIGN.md#action). Change it there and every part that extends it follows.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` | `false` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://www.reka-ui.com/docs/guides/composition) guide for more details. |
| `as` | `AsTag \| Component`: `ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> \| (props: any, ctx: Omit<{ attrs: Attrs; slots: Readonly<InternalSlots>; emit: (event: string, ...args: any[]) => void; expose: <Exposed extends Record<string, any> = Record<string, any>>(exposed?: Exposed \| undefined) => void; }, 'expose'>): any \| ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> \| 'a' \| 'button' \| 'div' \| 'form' \| 'h2' \| 'h3' \| 'img' \| 'input' \| 'label' \| 'li' \| 'nav' \| 'ol' \| 'p' \| 'span' \| 'svg' \| 'ul' \| 'template' \| {} & string` | `'button'` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `variant` | `'solid' \| 'outline' \| 'soft' \| 'ghost' \| 'link'` | `'solid'` | The visual style: a filled, outlined, tinted, background-free or text-link button. |
| `color` | `'neutral' \| 'primary' \| 'destructive' \| 'buy' \| 'checkout' \| 'inverse'` | `'primary'` | The colour role, e.g. primary for the brand colour, buy for add-to-cart, checkout for the checkout step, destructive for removing. |
| `size` | `'md' \| 'xs' \| 'sm' \| 'lg' \| 'icon-xs' \| 'icon-sm' \| 'icon-md' \| 'icon-lg'` | `'md'` | The button's height and padding; the `icon-*` sizes make a square, icon-only button. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-color` | `'neutral' \| 'primary' \| 'destructive' \| 'buy' \| 'checkout' \| 'inverse'` | Carries the `color` prop. |
| `data-size` | `'md' \| 'xs' \| 'sm' \| 'lg' \| 'icon-xs' \| 'icon-sm' \| 'icon-md' \| 'icon-lg'` | Carries the `size` prop. |
| `data-variant` | `'solid' \| 'outline' \| 'soft' \| 'ghost' \| 'link'` | Carries the `variant` prop. |
