Skip to content

Tooltip

A popup that appears when an element is hovered or focused, showing a hint for sighted users.

Usage guidelines

  • Prefer using tooltips as visual labels only: Tooltips should act as supplementary visual labels for sighted mouse and keyboard users. Tooltips alone are not accessible to touch or screen reader users. See Alternatives to tooltips for more details.
  • Provide an accessible name for the trigger: Tooltips are visual-only elements and are not a replacement for labeling the trigger. Use visible text when available, or an aria-label for icon-only triggers.

Anatomy

Import the component parts and assemble them:

vue
<script setup lang="ts">
import {
  TooltipArrow,
  TooltipPopup,
  TooltipPortal,
  TooltipPositioner,
  TooltipProvider,
  TooltipRoot,
  TooltipTrigger,
  TooltipViewport,
} from 'base-ui-vue'
</script>

<template>
  <TooltipProvider>
    <TooltipRoot>
      <TooltipTrigger />
      <TooltipPortal>
        <TooltipPositioner>
          <TooltipPopup>
            <TooltipArrow />
            <TooltipViewport />
          </TooltipPopup>
        </TooltipPositioner>
      </TooltipPortal>
    </TooltipRoot>
  </TooltipProvider>
</template>

Alternatives to tooltips

Tooltips should be supplementary popups that provide non-essential clarity in high-density UIs. A user should not miss critical information if they never see a tooltip.

Tooltips don't work well with touch input. Unlike mouse pointers with hover capability, there's no easily discoverable way to reveal a tooltip before tapping its trigger on a touch device.

iOS doesn't provide a system-standard, touch-friendly tooltip affordance, while Android may show a tooltip on long press. However, on the web, long press is often used to trigger contextual menus in the browser, which can lead to potential conflicts. For this reason, tooltips are disabled on touch devices.

Infotips

Popups that open when hovering an info icon should use a Popover-style component that can also be opened by touch and assistive technology users.

To know when to reach for a popover-style component instead of a tooltip, consider the purpose of the trigger element: if the trigger's purpose is to open the popup itself, it's a popover. If the trigger's purpose is unrelated to opening the popup, it's a tooltip.

Description text

Tooltips are designed for sighted users and are not a reliable way to deliver important information to touch users or assistive technologies. If the description is important to understanding the element, don't hide it behind a tooltip. Use inline text, or a Popover-style component if space is limited, so the information is accessible to everyone.

Since tooltips serve sighted mouse and keyboard users, iconography should clearly communicate the purpose of icon-only triggers, especially on mobile where the text label may not be visible.

If the description is not critical, a tooltip can still be used to provide extra clarity for sighted mouse or keyboard users.

Contextual feedback messages

Use a toast-style component for contextual feedback messages so the message can be announced to screen readers and support more complex content.

Examples

Detached triggers

A tooltip can be controlled by a trigger located either inside or outside the <TooltipRoot> component. For simple, one-off interactions, place the <TooltipTrigger> inside <TooltipRoot>, as shown in the example at the top of this page.

However, if defining the tooltip's content next to its trigger is not practical, you can use a detached trigger. This involves placing the <TooltipTrigger> outside of <TooltipRoot> and linking them with a handle created by the createTooltipHandle() function.

vue
<script setup lang="ts">
import { createTooltipHandle, TooltipRoot, TooltipTrigger } from 'base-ui-vue'

const tooltip = createTooltipHandle()
</script>

<template>
  <TooltipTrigger :handle="tooltip">
    Delete
  </TooltipTrigger>
  <TooltipRoot :handle="tooltip">
    <!-- portal, positioner, and popup -->
  </TooltipRoot>
</template>

Multiple triggers

A single tooltip can be opened by multiple trigger elements. You can achieve this by using the same handle for several detached triggers, or by placing multiple <TooltipTrigger> components inside a single <TooltipRoot>.

vue
<TooltipRoot>
  <TooltipTrigger>Trigger 1</TooltipTrigger>
  <TooltipTrigger>Trigger 2</TooltipTrigger>
  <!-- portal, positioner, and popup -->
</TooltipRoot>
vue
<script setup lang="ts">
import { createTooltipHandle, TooltipRoot, TooltipTrigger } from 'base-ui-vue'

const tooltip = createTooltipHandle()
</script>

<template>
  <TooltipTrigger :handle="tooltip">
    Trigger 1
  </TooltipTrigger>

  <TooltipTrigger :handle="tooltip">
    Trigger 2
  </TooltipTrigger>

  <TooltipRoot :handle="tooltip">
    <!-- portal, positioner, and popup -->
  </TooltipRoot>
</template>

The tooltip can render different content depending on which trigger opened it. This is achieved by passing a payload to the <TooltipTrigger> and reading it from the scoped slot exposed by <TooltipRoot>.

The payload can be strongly typed by providing a type argument to the createTooltipHandle() function:

vue
<script setup lang="ts">
import {
  createTooltipHandle,
  TooltipPopup,
  TooltipPortal,
  TooltipPositioner,
  TooltipRoot,
  TooltipTrigger,
} from 'base-ui-vue'

const tooltip = createTooltipHandle<{ text: string }>()
</script>

<template>
  <TooltipTrigger :handle="tooltip" :payload="{ text: 'Trigger 1' }">
    Trigger 1
  </TooltipTrigger>

  <TooltipTrigger :handle="tooltip" :payload="{ text: 'Trigger 2' }">
    Trigger 2
  </TooltipTrigger>

  <TooltipRoot :handle="tooltip" v-slot="{ payload }">
    <TooltipPortal>
      <TooltipPositioner :side-offset="8">
        <TooltipPopup>
          <span v-if="payload">
            Tooltip opened by {{ payload.text }}
          </span>
        </TooltipPopup>
      </TooltipPositioner>
    </TooltipPortal>
  </TooltipRoot>
</template>

Controlled mode with multiple triggers

You can control the tooltip's open state externally using the open prop and open-change event on <TooltipRoot>. This allows you to manage the tooltip's visibility based on your application's state. When using multiple triggers, you have to manage which trigger is active with the trigger-id prop on <TooltipRoot> and the id prop on each <TooltipTrigger>.

Note that there is no separate trigger-id-change event. Instead, the open-change event receives an additional details argument, which contains the trigger element that initiated the state change.

Animating the Tooltip

You can animate a tooltip as it moves between different trigger elements. This includes animating its position, size, and content.

Position and Size

To animate the tooltip's position, apply CSS transitions to the left, right, top, and bottom properties of the Positioner part. To animate its size, transition the width and height of the Popup part.

When content changes between triggers, you can use the Positioner's --positioner-width and --positioner-height CSS variables to keep the moving box stable while the popup resizes.

Content

The tooltip also supports content transitions. This is useful when different triggers display different content within the same tooltip.

To enable content animations, wrap the content in the <TooltipViewport> part. This part provides features to create direction-aware animations. It renders a div with a data-activation-direction attribute containing space-separated horizontal and vertical directions, such as right down or left up, that indicate the new trigger's position relative to the previous one.

Inside the <TooltipViewport>, the content is further wrapped in divs with data attributes to help with styling:

  • data-current: The currently visible content when no transitions are present or the incoming content.
  • data-previous: The outgoing content during a transition.

You can use these attributes to style the enter and exit animations.

API reference

Provider

Provides shared delays for a group of tooltips. The grouping logic ensures that once a tooltip becomes visible, the adjacent tooltips will be shown instantly.

PropTypeDefaultDescription
delaynumber600How long to wait before opening a tooltip, in milliseconds.
close-delaynumber0How long to wait before closing a tooltip, in milliseconds.
timeoutnumber400Another tooltip opens instantly if the previous tooltip closed within this timeout.

Root

Groups all parts of a tooltip. Does not render an HTML element.

PropTypeDefaultDescription
openbooleanundefinedWhether the tooltip is currently open.
default-openbooleanfalseWhether the tooltip is initially open.
disabledbooleanfalseWhether the tooltip should ignore interaction.
disable-hoverable-popupbooleanfalseWhether hovering the popup should keep it open.
track-cursor-axis'none' | 'x' | 'y' | 'both''none'Which cursor axis the tooltip should track.
handleTooltipHandleundefinedA handle that links detached triggers to the root.
trigger-idstring | nullundefinedThe active trigger id in controlled mode.
default-trigger-idstring | nullnullThe active trigger id for an initially open uncontrolled tooltip.
EmitsTypeDescription
open-change(open: boolean, details: EventDetails) => voidEmitted when the tooltip requests to open or close. Call details.cancel() to prevent the internal change.
open-change-complete(open: boolean) => voidEmitted after the popup's open or close animation completes.

Trigger

The element that opens the tooltip. Renders a <button> element.

PropTypeDefaultDescription
asstring | Component'button'The element or component to render.
native-buttonbooleantrueWhether the rendered element is a native button.
handleTooltipHandleundefinedAssociates this trigger with a detached tooltip root.
payloadunknownundefinedData exposed to the root scoped slot when this trigger opens the tooltip.
delaynumber600How long to wait before opening on hover.
close-on-clickbooleantrueWhether clicking an open trigger closes the tooltip.
close-delaynumber0How long to wait before closing after hover leaves.
disabledbooleanfalseWhether this trigger should ignore interaction.
idstringauto-generatedThe trigger id.
AttributeDescription
data-popup-openPresent when this trigger's tooltip is open.
data-trigger-disabledPresent when the trigger is disabled.

Portal

Teleports the tooltip contents. Uses Vue's native Teleport.

PropTypeDefaultDescription
keep-mountedbooleanfalseWhether to keep the portal contents mounted while hidden.
tostring | HTMLElement'body'Teleport target.
disabledbooleanfalseDisables Teleport while preserving mount behavior.

Positioner

Positions the tooltip against the active trigger. Renders a <div> element.

PropTypeDefaultDescription
asstring | Component'div'The element or component to render.
anchorHTMLElement | VirtualElement | nullactive triggerCustom anchor element.
position-method'absolute' | 'fixed''absolute'CSS positioning strategy.
side'top' | 'right' | 'bottom' | 'left''top'Preferred side of the trigger.
align'start' | 'center' | 'end''center'Preferred alignment on the side axis.
side-offsetnumber0Distance from the trigger on the side axis.
align-offsetnumber0Offset on the alignment axis.
collision-paddingPadding5Padding used when avoiding viewport collisions.
arrow-paddingPadding5Padding between the arrow and popup edge.
AttributeDescription
data-open / data-closedIndicates tooltip open state.
data-sideThe resolved side after collision handling.
data-alignThe resolved alignment after collision handling.
data-anchor-hiddenPresent when the anchor is hidden by clipping.
CSS VariableDescription
--available-width / --available-heightAvailable collision-aware space.
--anchor-width / --anchor-heightActive trigger dimensions.
--positioner-width / --positioner-heightPositioner dimensions.
--transform-originSuggested transform origin for popup animation.

The tooltip content container. Renders a <div role="tooltip"> element.

PropTypeDefaultDescription
asstring | Component'div'The element or component to render.
idstringauto-generatedThe popup id used by the trigger while open.
AttributeDescription
data-open / data-closedIndicates tooltip open state.
data-sideThe resolved side.
data-alignThe resolved alignment.
data-starting-stylePresent while animating in.
data-ending-stylePresent while animating out.
data-instantPresent when transitions should be skipped.

Arrow

Displays an arrow positioned against the active trigger. Renders a <div> element.

AttributeDescription
data-sideThe resolved side.
data-alignThe resolved alignment.
data-uncenteredPresent when the arrow could not be centered.
data-instantPresent when transitions should be skipped.

Viewport

Wraps payload-driven tooltip content in current and previous containers for content transitions.

AttributeDescription
data-activation-directionSpace-separated direction from the previous trigger to the current trigger.
data-transitioningPresent while previous and current content are both rendered for a transition.
data-instantPresent when transitions should be skipped.
data-currentApplied to the current content wrapper inside the viewport.
data-previousApplied to the outgoing content wrapper during a transition.
CSS VariableDescription
--popup-width / --popup-heightPrevious popup dimensions exposed on the data-previous wrapper.

createTooltipHandle

Creates a handle that links detached triggers to a tooltip root.

ts
const tooltip = createTooltipHandle<{ label: string }>()
APITypeDescription
open(triggerId)(triggerId: string) => voidOpens the tooltip for the trigger with the given id.
close()() => voidCloses the tooltip.
isOpenbooleanWhether the tooltip is currently open.