docs / core / tooltip

Tooltip

a short label that surfaces on hover or focus and says what a thing is: the name of an icon button, a keyboard shortcut, a word too clipped to read. never more than you can take in at a glance.

intensity · recedesrsc · client

provenance

authored in usva

layer

core / primitives

intensity

recedes · safe anywhere, including dense surfaces

composition

icon-only buttons, truncated text, terse toolbar actionsthe trigger's render prop puts it on an existing element instead of nesting a second buttonno interactive content inside. that is a Popovernever the only place information lives. it is a hint, not the label

a11y

opens on focus as well as hover · content is portalled with role="tooltip" and wired to the trigger via aria-describedby · escape dismisses · exit animation honors motion-reducejest-axe

dependencies

@base-ui/react
live demo · try it out
customize
contentthe label on hover and focus
sideOffsetgap from the trigger, in pixels
6
usagecopy
import { Tooltip, TooltipContent, TooltipTrigger } from "usva/primitives/tooltip";

<Tooltip>
  <TooltipTrigger render={<Button variant="outline">Hover me</Button>} />
  <TooltipContent>Deploys the current branch</TooltipContent>
</Tooltip>

props

proptypedefaultnotes
delaynumberhover delay in ms before opening. set on TooltipProvider, shared by every tooltip under it.
defaultOpenbooleanopen on mount, uncontrolled.
openbooleancontrolled open state.
onOpenChange(open: boolean) => voidfires when the open state changes.
sideOffsetnumber6distance in px between trigger and content, on TooltipContent.

get it

npx shadcn add https://usva.build/r/tooltip.jsoncopy
source · components/ui/tooltip.tsxexactly what this command copiescopy
"use client";
import { Tooltip as Base } from "@base-ui/react/tooltip";
import * as React from "react";
import { cn } from "@/lib/utils";
import { useScopedTheme } from "./use-scoped-theme";

export const TooltipProvider = Base.Provider;
export const Tooltip = Base.Root;
export const TooltipTrigger = Base.Trigger;

export const TooltipContent = React.forwardRef<
  HTMLDivElement,
  React.ComponentPropsWithoutRef<typeof Base.Popup> & { sideOffset?: number }
>(({ className, sideOffset = 6, children, ...props }, ref) => {
  const [probe, theme] = useScopedTheme();

  return (
    <>
      <span ref={probe} hidden />
      <Base.Portal>
        <Base.Positioner
          data-theme={theme}
          sideOffset={sideOffset}
          className="z-overlay"
        >
          <Base.Popup
            ref={ref}
            className={cn(
              "rim-light rounded-md border border-border bg-surface-2 px-2.5 py-1.5 font-mono text-[11px] leading-none text-ink shadow-raised",
              "transition-enter duration-fast ease-soft motion-reduce:transition-none motion-reduce:transform-none",
              "data-[starting-style]:translate-y-0.5 data-[starting-style]:opacity-0",
              "data-[ending-style]:translate-y-0.5 data-[ending-style]:opacity-0",
              className,
            )}
            {...props}
          >
            {children}
          </Base.Popup>
        </Base.Positioner>
      </Base.Portal>
    </>
  );
});
TooltipContent.displayName = "TooltipContent";