GitHub

Tooltip

Contextual hint on hover/focus for compact interfaces.

01 Live Demo

02 Code Snippet

src/ui/Tooltip.tsx
import { Tooltip } from "@/ui/Tooltip";

<Tooltip.Root side="top">
  <Tooltip.Trigger>
    <button type="button">Hover me</button>
  </Tooltip.Trigger>
  <Tooltip.Content>Helpful context for this action</Tooltip.Content>
</Tooltip.Root>

03 Copy-Paste (Single File)

Tooltip.tsx
import { createContext, useContext, useState, type ReactNode } from "react";

const Ctx = createContext<{ open: boolean; setOpen: (open: boolean) => void } | null>(null);

function TooltipRoot({ children }: { children: ReactNode }) {
  const [open, setOpen] = useState(false);
  return <Ctx.Provider value={{ open, setOpen }}><span className="relative inline-flex">{children}</span></Ctx.Provider>;
}

function TooltipTrigger({ children }: { children: ReactNode }) {
  const ctx = useContext(Ctx);
  if (!ctx) throw new Error("Use inside Tooltip.Root");
  return <span onMouseEnter={() => ctx.setOpen(true)} onMouseLeave={() => ctx.setOpen(false)}>{children}</span>;
}

function TooltipContent({ children }: { children: ReactNode }) {
  const ctx = useContext(Ctx);
  if (!ctx || !ctx.open) return null;
  return <div className="absolute bottom-[calc(100%+8px)] left-1/2 -translate-x-1/2 rounded-md bg-slate-900 px-2.5 py-1.5 text-xs text-white">{children}</div>;
}

type TooltipCompound = typeof TooltipRoot & { Root: typeof TooltipRoot; Trigger: typeof TooltipTrigger; Content: typeof TooltipContent };
export const Tooltip = Object.assign(TooltipRoot, { Root: TooltipRoot, Trigger: TooltipTrigger, Content: TooltipContent }) as TooltipCompound;
react-principles