# Tooltip

A small label that appears next to an element on hover or keyboard focus.

Tooltip is an open-source component from suiss/ui for React. The @esuiss/ui CLI copies its source into your project, so you own the code.

Source: https://ui.suiss.app/docs/components/tooltip

**React**

**Example**

```tsx
import { Button } from "@/components/ui/button"
import {
  Tooltip,
  TooltipContent,
  TooltipProvider,
  TooltipTrigger,
} from "@/components/ui/tooltip"

export default function TooltipDemo() {
  return (
    <TooltipProvider>
      <div className="p-12">
        <Tooltip>
          <TooltipTrigger render={<Button variant="outline" />}>
            Hover me
          </TooltipTrigger>
          <TooltipContent side="top">Add to library</TooltipContent>
        </Tooltip>
      </div>
    </TooltipProvider>
  )
}
```

`Tooltip` wraps the tooltip primitive of the chosen base. `TooltipTrigger` is the element that shows the tooltip, and `TooltipContent` renders the dark label with an arrow in a portal, positioned beside the trigger. `TooltipProvider` shares open delays across a group of tooltips.

Use tooltips to name icon-only buttons or show a keyboard shortcut. Keep the text short and don't put essential information or interactive content in a tooltip; use `Popover` for that.

## Installation

### CLI

```bash title="Terminal"
npx shadcn@latest add @esuiss/tooltip
```

Works in any project with a `components.json`. See [Installation](https://ui.suiss.app/docs/installation#add-components) for the one-line registry setup. Or use the suiss/ui CLI:

```bash title="Terminal"
npx @esuiss/ui@latest add tooltip
```

### Manual

1. Install the dependencies and add the [`cn` helper](https://ui.suiss.app/docs/installation#the-cn-helper):

```bash title="Terminal"
npm install @base-ui/react
```

2. Copy the source into `components/ui/tooltip.tsx`:

```tsx title="components/ui/tooltip.tsx"
"use client"

import { Tooltip as TooltipPrimitive } from "@base-ui/react/tooltip"
import { cn } from "@/lib/utils"

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(
            "z-50 inline-flex w-fit max-w-xs origin-(--transform-origin) items-center gap-1.5 rounded-xl bg-foreground px-3 py-1.5 text-xs text-background has-data-[slot=kbd]:pe-1.5 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-start-2 data-[side=inline-start]:slide-in-from-end-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 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
            className
          )}
          {...props}
        >
          {children}
          <TooltipPrimitive.Arrow className="z-50 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-xs bg-foreground fill-foreground data-[side=bottom]:top-1 data-[side=inline-end]:top-1/2! data-[side=inline-end]:-inset-s-1 data-[side=inline-end]:translate-x-[1.5px] rtl:data-[side=inline-end]:translate-x-[-1.5px] data-[side=inline-end]:-translate-y-1/2 data-[side=inline-start]:top-1/2! data-[side=inline-start]:-inset-e-1 data-[side=inline-start]:translate-x-[-1.5px] rtl:data-[side=inline-start]:translate-x-[1.5px] data-[side=inline-start]:-translate-y-1/2 data-[side=left]:top-1/2! data-[side=left]:-right-1 data-[side=left]:translate-x-[-1.5px] data-[side=left]:-translate-y-1/2 data-[side=right]:top-1/2! data-[side=right]:-left-1 data-[side=right]:translate-x-[1.5px] data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-2.5" />
        </TooltipPrimitive.Popup>
      </TooltipPrimitive.Positioner>
    </TooltipPrimitive.Portal>
  )
}

export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger }
```

3. Update the import paths to match your project.

## Usage

```tsx
import { TooltipProvider, Tooltip, TooltipTrigger, TooltipContent } from "@/components/ui/tooltip"
```

```tsx
<TooltipProvider>
  <div className="p-12">
    <Tooltip>
      <TooltipTrigger render={<Button variant="outline" />}>
        Hover me
      </TooltipTrigger>
      <TooltipContent side="top">Add to library</TooltipContent>
    </Tooltip>
  </div>
</TooltipProvider>
```

Set `side` on `TooltipContent` to place the tooltip on another side; it opens above the trigger by default, `4px` away. Place a `Kbd` inside `TooltipContent` to show a shortcut.

Use the `render` prop on `TooltipTrigger` to render it as a `Button`.

Wrap your app, or a toolbar of tooltips, in `TooltipProvider`. This wrapper sets the provider's `delay` to `0`, so tooltips open without waiting.

## Examples

### Sides

Places tooltips on each of the four sides of their triggers.

**Example**

```tsx
import { Button } from "@/components/ui/button"
import {
  Tooltip,
  TooltipContent,
  TooltipProvider,
  TooltipTrigger,
} from "@/components/ui/tooltip"

export default function TooltipSides() {
  return (
    <TooltipProvider>
      <div className="p-12">
        <div className="grid grid-cols-2 place-items-center gap-x-24 gap-y-16">
          {(["top", "right", "left", "bottom"] as const).map((side) => (
            <TooltipProvider key={side}>
              <Tooltip>
                <TooltipTrigger render={<Button variant="outline" />}>
                  {side}
                </TooltipTrigger>
                <TooltipContent side={side}>Shown on the {side}</TooltipContent>
              </Tooltip>
            </TooltipProvider>
          ))}
        </div>
      </div>
    </TooltipProvider>
  )
}
```

### With shortcut

Names an icon button and shows its keyboard shortcut with `Kbd`.

**Example**

```tsx
import { PlusIcon } from "@phosphor-icons/react"
import { Button } from "@/components/ui/button"
import { Kbd } from "@/components/ui/kbd"
import {
  Tooltip,
  TooltipContent,
  TooltipProvider,
  TooltipTrigger,
} from "@/components/ui/tooltip"

export default function TooltipWithShortcut() {
  return (
    <TooltipProvider>
      <div className="p-12">
        <Tooltip>
          <TooltipTrigger
            render={
              <Button variant="outline" size="icon" aria-label="New file" />
            }
          >
            <PlusIcon />
          </TooltipTrigger>
          <TooltipContent side="top">
            New file
            <Kbd mod>N</Kbd>
          </TooltipContent>
        </Tooltip>
      </div>
    </TooltipProvider>
  )
}
```

## API Reference

- `TooltipProvider`
- `Tooltip`
  - `TooltipTrigger`
  - `TooltipContent`

### TooltipProvider

Shares delay timing across the tooltips inside it.

Also accepts the props of the primitive part it wraps.

### Tooltip

The root that holds a tooltip's open state.

Also accepts the props of the primitive part it wraps.

### TooltipTrigger

The element that shows the tooltip on hover or focus.

Also accepts the props of the primitive part it wraps.

### TooltipContent

The positioned label with its arrow, rendered in a portal.

Also accepts the props of the primitive parts it wraps.

## Accessibility

- Tooltips open when the trigger is hovered or receives keyboard focus, and close when the pointer leaves.
- `Escape` closes an open tooltip while focus stays on the trigger.
- A tooltip doesn't name its trigger. Give icon-only triggers their own `aria-label`.

## Notes

- Parts carry `data-slot` attributes such as `tooltip-trigger` and `tooltip-content`; the popup and arrow set `data-side`.
- The label uses `bg-foreground` with `text-background`, so it inverts against the page in both light and dark appearances.
- `TooltipContent` accepts the positioner props `side`, `sideOffset`, `align` and `alignOffset`.

