Components / Primitives

Tooltip

A short hint on hover or focus that grows out of its trigger; neighbours open instantly.

pnpm exec shadcn add @intelligo/tooltip

Inside an app made by intelligo create, or any shadcn project that names the registry.

built on

ai-motion

npm packages it adds

@base-ui/react · motion

used by these pages

Chat

Source

What shadcn add writes to components/ui/tooltip.tsx. It is yours from then on.

"use client";

/*
 * The tooltip: a light surface that grows out of its trigger and blurs in;
 * once one has shown, the provider opens neighbouring tooltips instantly.
 * It springs open with motion; the root is kept controlled so the exit
 * plays.
 */

import * as React from "react";
import { Tooltip as TooltipPrimitive } from "@base-ui/react/tooltip";
import { AnimatePresence, motion, useReducedMotion } from "motion/react";

import { popupMotion, useOpenState } from "@showcase/components/ui/ai-motion";
import { cn } from "@showcase/lib/utils";

const TooltipOpenContext = React.createContext<boolean | null>(null);

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

function Tooltip({
  open: openProp,
  defaultOpen,
  onOpenChange,
  ...props
}: TooltipPrimitive.Root.Props) {
  const [open, setOpen] = useOpenState(openProp, defaultOpen, onOpenChange);
  return (
    <TooltipOpenContext.Provider value={open}>
      <TooltipPrimitive.Root
        data-slot="tooltip"
        open={open}
        onOpenChange={setOpen}
        {...props}
      />
    </TooltipOpenContext.Provider>
  );
}

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

function TooltipContent({
  className,
  side = "top",
  sideOffset = 6,
  align = "center",
  alignOffset = 0,
  children,
  ...props
}: TooltipPrimitive.Popup.Props &
  Pick<
    TooltipPrimitive.Positioner.Props,
    "align" | "alignOffset" | "side" | "sideOffset"
  >) {
  const open = React.useContext(TooltipOpenContext) ?? true;
  const reduced = useReducedMotion() ?? false;

  return (
    <AnimatePresence>
      {open && (
        <TooltipPrimitive.Portal keepMounted>
          <TooltipPrimitive.Positioner
            align={align}
            alignOffset={alignOffset}
            side={side}
            sideOffset={sideOffset}
            className="isolate z-popover"
          >
            <TooltipPrimitive.Popup
              data-slot="tooltip-content"
              render={<motion.div {...popupMotion(reduced, 0.88)} />}
              className={cn(
                "inline-flex w-fit max-w-xs origin-(--transform-origin) items-center gap-1.5 rounded-lg border border-border bg-popover px-2.5 py-1 text-xs font-medium text-popover-foreground shadow-lg has-data-[slot=kbd]:pr-1",
                className
              )}
              {...props}
            >
              {children}
            </TooltipPrimitive.Popup>
          </TooltipPrimitive.Positioner>
        </TooltipPrimitive.Portal>
      )}
    </AnimatePresence>
  );
}

export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };

Ready to try it? Three commands and the app runs against a stub model.