Skip to content
Dethink Components

ComponentsTooltip

Tooltip

Show a short hint when users hover over or focus a control.

On this page

Install component source from the public registry, or use the local workspace package. Follow the setup guide first. The npm package is not published yet.

View registry files
  • React Aria (react-aria)

    Accessibility hooks and overlay utilities used by the implementation.

  • React Aria Components (react-aria-components)

    Accessible interaction primitives, including keyboard behavior, focus handling, and ARIA semantics.

Shared foundation: React, clsx for conditional classes, tailwind-merge for resolving utility-class conflicts. Styles use Tailwind CSS and Dethink CSS variables.

Based on this component’s source and registry composition. Libraries used only in showcase examples are excluded.

Import the component into your page or component file.

Usage
import {
  Tooltip,
  TooltipContent,
  TooltipTrigger,
} from "@dethink/components";

Try the examples, then open the code to use them in your app. Tab to a trigger — tooltips show on focus too.

Basic

An icon-only trigger keeps its aria-label; the tooltip adds the visible hint.

Show sourceexamples/tooltip/basic.tsx
examples/tooltip/basic.tsx
"use client";

import { Tooltip, TooltipContent, TooltipTrigger } from "@dethink/components";
import { RefreshCw } from "lucide-react";

export function TooltipBasic() {
  return (
    <div className="flex justify-center">
      <Tooltip>
        <TooltipTrigger
          aria-label="Refresh dashboard"
          size="icon"
          variant="outline"
        >
          <RefreshCw className="size-4" aria-hidden="true" />
        </TooltipTrigger>
        <TooltipContent>Refresh dashboard data</TooltipContent>
      </Tooltip>
    </div>
  );
}

Timing and placement

delay and closeDelay tune hover intent; placement and showArrow position the hint.

Show sourceexamples/tooltip/timing.tsx
examples/tooltip/timing.tsx
"use client";

import { Tooltip, TooltipContent, TooltipTrigger } from "@dethink/components";

export function TooltipTiming() {
  return (
    <div className="flex flex-wrap justify-center gap-3">
      <Tooltip delay={0} closeDelay={0}>
        <TooltipTrigger variant="soft" size="sm">
          Instant
        </TooltipTrigger>
        <TooltipContent>Shows immediately</TooltipContent>
      </Tooltip>
      <Tooltip delay={700} closeDelay={300}>
        <TooltipTrigger variant="soft" size="sm">
          Patient
        </TooltipTrigger>
        <TooltipContent>700ms open delay, 300ms close delay</TooltipContent>
      </Tooltip>
      <Tooltip>
        <TooltipTrigger variant="soft" size="sm">
          Placed top
        </TooltipTrigger>
        <TooltipContent placement="top" showArrow>
          Anchored above with an arrow
        </TooltipContent>
      </Tooltip>
    </div>
  );
}

Examples that combine components for common tasks.

Formatting toolbar with shortcut hints

Tooltips in their strongest seat: an icon-only toolbar where each hint pairs the action name with its keyboard shortcut, while aria-label and aria-pressed keep the buttons fully accessible without the tooltip.

Show sourceexamples/tooltip/recipe-toolbar.tsx
examples/tooltip/recipe-toolbar.tsx
"use client";

import { useState } from "react";
import { Tooltip, TooltipContent, TooltipTrigger } from "@dethink/components";
import { Bold, Italic, Link2, Strikethrough } from "lucide-react";

const tools = [
  { id: "bold", label: "Bold", keys: "⌘B", icon: Bold },
  { id: "italic", label: "Italic", keys: "⌘I", icon: Italic },
  { id: "strike", label: "Strikethrough", keys: "⌘⇧X", icon: Strikethrough },
  { id: "link", label: "Add link", keys: "⌘K", icon: Link2 },
];

/**
 * Icon-only toolbars are where tooltips earn their keep: each trigger keeps
 * a real aria-label (the tooltip is a hint, not the accessible name) and the
 * content pairs the label with its keyboard shortcut.
 */
export function TooltipRecipeToolbar() {
  const [active, setActive] = useState<Set<string>>(new Set(["bold"]));

  return (
    <div
      role="toolbar"
      aria-label="Text formatting"
      className="border-border bg-muted/40 mx-auto flex w-fit gap-1 rounded-lg border p-1"
    >
      {tools.map((tool) => (
        <Tooltip key={tool.id} delay={300}>
          <TooltipTrigger
            aria-label={tool.label}
            aria-pressed={active.has(tool.id)}
            size="icon"
            variant={active.has(tool.id) ? "soft" : "ghost"}
            onClick={() =>
              setActive((current) => {
                const next = new Set(current);
                if (next.has(tool.id)) {
                  next.delete(tool.id);
                } else {
                  next.add(tool.id);
                }
                return next;
              })
            }
          >
            <tool.icon className="size-4" aria-hidden="true" />
          </TooltipTrigger>
          <TooltipContent placement="top">
            <span className="flex items-center gap-2">
              {tool.label}
              <kbd className="border-border/60 rounded border px-1 font-mono text-[0.7em]">
                {tool.keys}
              </kbd>
            </span>
          </TooltipContent>
        </Tooltip>
      ))}
    </div>
  );
}

Tooltip coordinates timing; the trigger is a real Button and the content carries placement.

Tooltip anatomy
PropWhat it doesDefault
delay / closeDelaynumber (ms)Hover intent timing before showing and hiding.library defaults
open / onOpenChangeboolean / (open) => voidControlled visibility for programmatic tooltips.Not set
disabledbooleanSuppresses the tooltip without disabling the trigger.false
TooltipTriggervariant + size (Button API)A real button trigger — keep the accessible name on the trigger (aria-label); the tooltip is a hint, not the name.Not set
TooltipContent — placement"top" | "bottom" | "left" | "right" (+ alignment)Preferred side relative to the trigger."top"
TooltipContent — showArrowbooleanRenders a pointing arrow.false