Components
Tooltip
A short text hint for the element it wraps, shown on hover and on keyboard focus. Inverse surface, the opposite of the current mode (light in dark mode, dark in light mode), with a medium shadow and 13px text. Text only: anything a user acts on belongs in a popover or a dialog.
Preview
import { IconLink } from "@tabler/icons-react"
import { Button } from "@/components/ui/button"
import { Tooltip } from "@/components/ui/tooltip"
<Tooltip content="Copy link">
<Button variant="secondary" iconOnly aria-label="Copy link">
<IconLink size={16} weight="regular" />
</Button>
</Tooltip>Usage
When to use
- Name what an icon button does for sighted users, in the same words as its aria-label.
- Show a detail some users want but nobody needs, such as the full date behind 2 hours ago.
When not to use
- Anything a user must read to finish a task. Write it on the page, or in the description of a Field.
- Links, buttons or other controls inside the hint. Use Dialog for anything the user acts on.
- The only name of an icon button. Give the button aria-label as well.
Variants
side
Side of the trigger the tooltip opens on. It flips when there is no room.
import { IconLink } from "@tabler/icons-react"
import { Button } from "@/components/ui/button"
import { Tooltip } from "@/components/ui/tooltip"
<Tooltip content="Copy link">
<Button variant="secondary" iconOnly aria-label="Copy link">
<IconLink size={16} weight="regular" />
</Button>
</Tooltip>
<Tooltip content="Copy link" side="right">
<Button variant="secondary" iconOnly aria-label="Copy link">
<IconLink size={16} weight="regular" />
</Button>
</Tooltip>
<Tooltip content="Copy link" side="bottom">
<Button variant="secondary" iconOnly aria-label="Copy link">
<IconLink size={16} weight="regular" />
</Button>
</Tooltip>
<Tooltip content="Copy link" side="left">
<Button variant="secondary" iconOnly aria-label="Copy link">
<IconLink size={16} weight="regular" />
</Button>
</Tooltip>align
Alignment along the trigger's edge.
import { IconLink } from "@tabler/icons-react"
import { Button } from "@/components/ui/button"
import { Tooltip } from "@/components/ui/tooltip"
<Tooltip content="Copy link" align="start">
<Button variant="secondary" iconOnly aria-label="Copy link">
<IconLink size={16} weight="regular" />
</Button>
</Tooltip>
<Tooltip content="Copy link">
<Button variant="secondary" iconOnly aria-label="Copy link">
<IconLink size={16} weight="regular" />
</Button>
</Tooltip>
<Tooltip content="Copy link" align="end">
<Button variant="secondary" iconOnly aria-label="Copy link">
<IconLink size={16} weight="regular" />
</Button>
</Tooltip>Examples
Default
import { IconLink } from "@tabler/icons-react"
import { Button } from "@/components/ui/button"
import { Tooltip } from "@/components/ui/tooltip"
<Tooltip content="Copy link">
<Button variant="secondary" iconOnly aria-label="Copy link">
<IconLink size={16} />
</Button>
</Tooltip>Extra detail
A full date behind a relative one, for the few users who want it.
import { Button } from "@/components/ui/button"
import { Tooltip } from "@/components/ui/tooltip"
<Tooltip content="6 October 2026, 14:32" side="bottom">
<Button variant="ghost" size="sm">Edited 2 hours ago</Button>
</Tooltip>Do and don't
Do
- Name what an icon button does, in the same words as its aria-label.
- Add a fact the user may want but doesn't need: a full date behind a relative one, a shortcut.
- Keep it to a few words on one line.
Don't
- Don't put links, buttons or other controls in a tooltip.
- Don't use a tooltip as the only name of an icon button.
- Don't put anything a user needs to finish a task only in a tooltip.
- Don't wrap an element that can't take focus, such as plain text or a disabled button.
Props
| Name | Type | Values | Default | Description |
|---|---|---|---|---|
| content required | node | - | - | The hint: a few words of plain text, sentence case, no period for a fragment. Copy link, Archived 3 days ago. |
| side | enum | top | right | bottom | left | top | Side of the trigger the tooltip opens on. It flips when there is no room. |
| align | enum | start | center | end | center | Alignment along the trigger's edge. |
| sideOffset | number | - | 4 | Gap between the trigger and the tooltip in px; 4 matches spacing-xs. |
| delayDuration | number | - | 200 | Wait in ms before a hover opens the tooltip. Focus opens it at once. |
| disabled | boolean | - | false | Renders the trigger without a tooltip. |
| open | boolean | - | - | Controlled open state, for a tour or a test. Pair it with onOpenChange. |
| defaultOpen | boolean | - | - | Open on first render, when you don't control it. |
| onOpenChange | function | - | - | Called with true or false when hover, focus or Escape opens or closes the tooltip. |
Slots
| Name | Required | Description |
|---|---|---|
| trigger | yes | One element that takes focus: a Button, a link. The tooltip describes it. |
Anatomy
| Part | Description |
|---|---|
| root | The tooltip: inverse fill (light on a dark page, dark on a light one) with inverse text, a transparent border (it shows in forced-colors mode, which drops the fill), radius-element, shadow-med, 13px text, at most 320px wide. |
| trigger | The child element it describes. Gets aria-describedby while the tooltip is open. |
Accessibility
Role: tooltip
| Keys | Action |
|---|---|
| Tab | Focus on the trigger opens the tooltip at once. |
| Escape | Closes the tooltip; focus stays on the trigger. |
- The trigger must take focus (a button or a link), so keyboard users get the hint too. The tooltip text is read as the trigger's description.
- A tooltip never names an icon button on its own: the button still needs aria-label.
- Hover doesn't exist on touch screens. Don't put anything a user needs only in a tooltip.
- The tooltip stays open while the pointer moves onto it, so it can be read at any zoom.
Install
npx shadcn add https://werewolf-ui.vercel.app/r/tooltip.jsonThis also adds styles/werewolf.css, the tokens every component uses, and any other component it needs. Import that file once in your root CSS, after @import "tailwindcss", with a path relative to that CSS file (from app/globals.css it is @import "../styles/werewolf.css").