Tooltip

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

"use client"

import { Tooltip as TooltipPrimitive } from "@animate-ui/react/primitives/base-ui/tooltip"
import { cn } from "cn"
import { Button } from "@/components/ui/button"

function TooltipProvider({
  delay = 0,
  ...props
}: TooltipPrimitive.Provider.Props) {
  return (
    <TooltipPrimitive.Provider
      data-slot="tooltip-provider"
      delay={delay}
      {...props}
    />
  )
}

function Tooltip({ ...props }: TooltipPrimitive.Root.Props) {
  return <TooltipPrimitive.Root data-slot="tooltip" {...props} />
}

function TooltipTrigger({ ...props }: TooltipPrimitive.Trigger.Props) {
  return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />
}

function TooltipContent({
  className,
  side = "top",
  sideOffset = 4,
  align = "center",
  alignOffset = 0,
  children,
  ...props
}: TooltipPrimitive.Popup.Props &
  Pick<
    TooltipPrimitive.Positioner.Props,
    "align" | "alignOffset" | "side" | "sideOffset"
  >) {
  return (
    <TooltipPrimitive.Portal>
      <TooltipPrimitive.Positioner
        align={align}
        alignOffset={alignOffset}
        side={side}
        sideOffset={sideOffset}
        className="isolate z-50"
      >
        <TooltipPrimitive.Popup
          data-slot="tooltip-content"
          className={cn(
            "inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs has-data-[slot=kbd]:pr-1.5 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm z-50 w-fit max-w-xs origin-(--transform-origin) bg-foreground text-background",
            className
          )}
          {...props}
        >
          {children}
          <TooltipPrimitive.Arrow className="size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px] data-[side=inline-end]:top-1/2! data-[side=inline-end]:-left-1 data-[side=inline-end]:-translate-y-1/2 data-[side=inline-start]:top-1/2! data-[side=inline-start]:-right-1 data-[side=inline-start]:-translate-y-1/2 z-50 bg-foreground fill-foreground data-[side=bottom]:top-1 data-[side=left]:top-1/2! data-[side=left]:-right-1 data-[side=left]:-translate-y-1/2 data-[side=right]:top-1/2! data-[side=right]:-left-1 data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-2.5" />
        </TooltipPrimitive.Popup>
      </TooltipPrimitive.Positioner>
    </TooltipPrimitive.Portal>
  )
}

export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }

export default function TooltipDemo({
  followCursor = false,
  side = "top",
  sideOffset = 4,
  align = "center",
  alignOffset = 0,
}: {
  followCursor?: boolean | "x" | "y"
  side?: "top" | "right" | "bottom" | "left" | "inline-start" | "inline-end"
  sideOffset?: number
  align?: "start" | "center" | "end"
  alignOffset?: number
}) {
  return (
    <TooltipProvider>
      <Tooltip followCursor={followCursor}>
        <TooltipTrigger render={<Button variant="outline" />}>
          Hover
        </TooltipTrigger>
        <TooltipContent
          side={side}
          sideOffset={sideOffset}
          align={align}
          alignOffset={alignOffset}
        >
          <p>Add to library</p>
        </TooltipContent>
      </Tooltip>
    </TooltipProvider>
  )
}

Installation

Install the packages

npm install @animate-ui/react motion
pnpm add @animate-ui/react motion
yarn add @animate-ui/react motion
bun add @animate-ui/react motion

Add the shadcn/ui component

npx shadcn@latest add tooltip
pnpm dlx shadcn@latest add tooltip
yarn dlx shadcn@latest add tooltip
bun x shadcn@latest add tooltip

Animate it

The CLI finds your shadcn/ui components through components.json, swaps their primitive imports for Animate UI's and removes the CSS animation classes Motion replaces.

npx @animate-ui/cli@latest animate tooltip
pnpm dlx @animate-ui/cli@latest animate tooltip
yarn dlx @animate-ui/cli@latest animate tooltip
bun x @animate-ui/cli@latest animate tooltip

Usage

Import the primitive from Animate UI instead of Base UI. Everything else stays the same.

import { Tooltip } from '@animate-ui/react/primitives/base-ui/tooltip';

export function BoldButton() {
  return (
    <Tooltip.Provider delay={0}>
      <Tooltip.Root>
        <Tooltip.Trigger aria-label="Bold">B</Tooltip.Trigger>
        <Tooltip.Portal>
          <Tooltip.Positioner sideOffset={4}>
            <Tooltip.Popup>
              Bold
              <Tooltip.Arrow />
            </Tooltip.Popup>
          </Tooltip.Positioner>
        </Tooltip.Portal>
      </Tooltip.Root>
    </Tooltip.Provider>
  );
}

Using the @base-ui/react umbrella import instead? The barrel mirrors it: import { Tooltip } from '@animate-ui/react/primitives/base-ui'.

Animation

The motion of Animate UI v1's Base UI Tooltip, starting from 0.7, with a warm-up:

  • Popup grows from 0.7 and fades in, from the trigger (--transform-origin), on a spring (stiffness 400, damping 29) that overshoots a little before it settles.
  • It closes the same way: it shrinks back to 0.7 on the same spring while it fades out, then unmounts.
  • Tooltips warm up: once one is open, moving along a toolbar shows the next one at once, without its entrance, and hides the previous one at once. 300ms after the last one closed, the next one plays its entrance again. shadcn/ui gives each tooltip its own Provider, so the library's own grouping never sees it: Animate UI tracks it across all of its tooltips.
  • Base UI only opens it on hover for mouse-like pointers, and on keyboard focus.
  • With keepMounted on the Portal, the closed popup stays in the DOM: it plays its exit, then is hidden.
  • Your own render element (or function) on the Popup is made animatable rather than replaced.
  • Open from the start (defaultOpen), it plays the same entrance on page load, like v1.
  • With followCursor on the Root, the popup leans towards the pointer while it moves over the trigger, by half its offset from the trigger's center, on a spring (stiffness 200, damping 17): on both axes, or only 'x' or 'y', as in v1. Under reduced motion it follows without the spring.
  • Under reduced motion, it only fades (no scale).

API Reference

Every part takes exactly the props of Base UI's Tooltip. Animate UI adds the animation props below.

Root

Base UI props, plus:

PropTypeDefault
followCursor?
boolean | 'x' | 'y'false
followCursorSpringOptions?
SpringOptions{ stiffness: 200, damping: 17 }

Provider, Trigger, Portal, Positioner, Arrow, Viewport

Base UI props only. They are not animated.

Base UI props, plus:

PropTypeDefault
transition?
Transition{ type: 'spring', stiffness: 400, damping: 29 }
exitTransition?
Transitiontransition

Built by Skyleen. The source code is available on GitHub.

Last updated: 10/18/2026

On this page