Hover and focus label rendered in a portal: it scales in from its trigger on a soft spring, flips to the side with room, carries a title, description, arrow and shortcut, and hands off instantly between neighbours.
tooltiphintkbdshortcuttoolbarpopoverportal
Installation
$npx shadcn@latest add @geturui/tooltip
Usage
usage.tsx
1import { Bold, CircleHelp } from "lucide-react";
2import { Tooltip, TooltipProvider } from "@/components/ui/tooltip";
3
4export function Toolbar() {
5 return (
6 <TooltipProvider>
7 <Tooltip content="Bold" kbd="⌘B" side="bottom">
8 <button type="button" aria-label="Bold" className="grid size-8 place-items-center rounded-sm">
9 <Bold className="size-4" aria-hidden />
10 </button>
11 </Tooltip>
12 <Tooltip title="How seats are billed" description="Only members active this month count." arrow>
13 <button type="button" aria-label="How seats are billed">
14 <CircleHelp className="size-4" aria-hidden />
15 </button>
16 </Tooltip>
17 </TooltipProvider>
18 );
19}
Props
| Prop | Type | Default | Description |
|---|---|---|---|
content | ReactNode | — | Tooltip label (alias: title). |
title | ReactNode | — | Same as content; reads better alongside description. |
description | ReactNode | — | Supporting text under the title; the bubble widens to 260px and wraps. |
children* | ReactElement | — | The trigger. Must accept aria-describedby (forward it if it's your own component). |
kbd | string | — | Keyboard shortcut shown beside the label. |
side | "top" | "right" | "bottom" | "left" | "top" | Preferred side; flips to the opposite side when there is no room. |
align | "start" | "center" | "end" | "center" | Alignment along the side; the bubble also shifts to stay 8px inside the viewport. |
arrow | boolean | false | Small arrow that keeps pointing at the trigger after shifts. |
sideOffset | number | 8 | Gap between trigger and bubble, px. |
delay | number | 120 | Milliseconds before it opens. Skipped while a neighbouring tooltip was just open. |
closeDelay | number | 80 | Milliseconds before it closes after the pointer leaves, so the pointer can reach the bubble. |
open / defaultOpen / onOpenChange | boolean / boolean / (open: boolean) => void | — | Controlled or uncontrolled open state. |
disabled | boolean | false | Never open. |
className | string | — | Classes for the tooltip bubble. |
TooltipProvider | { delay?, closeDelay?, skipDelay? = 300 } | — | Optional wrapper that groups tooltips and sets shared delays. Without it, tooltips share a page-wide group. |
Accessibility
- Opens on keyboard focus (focus-visible only, not after a mouse press) as well as hover; closes on blur, on pressing the trigger, and on Escape from anywhere.
- The bubble has role="tooltip" and is linked to the trigger with aria-describedby while open, merged with any describedby the trigger already has.
- The pointer can move onto the bubble without it closing (WCAG 1.4.13). On touch, a long press opens it.
- Supplementary only: icon-only triggers still need their own aria-label.