dotUI
dotUI
beta
  1. Components
  2. Overlay
  3. Tooltip

Tooltip

Tooltip displays a description of an element on hover or focus.

import { Button } from "@/lib/components/core/default/button";
import { Tooltip } from "@/lib/components/core/default/tooltip";
import { PlusIcon } from "@/lib/icons";

function Demo() {
  return (
    <Tooltip content="Add to library">
      <Button shape="square">
        <PlusIcon />
      </Button>
    </Tooltip>
  );
}

Installation

Install the following dependencies:

npm install react-aria-components

Copy and paste the following code into your project.

"use client";

import * as React from "react";
import {
  Tooltip as AriaTooltip,
  TooltipTrigger as AriaTooltipTrigger,
  OverlayArrow as AriaOverlayArrow,
  type TooltipProps as AriaTooltipProps,
  type TooltipTriggerComponentProps as AriaTooltipTriggerProps,
} from "react-aria-components";
import { tv, type VariantProps } from "tailwind-variants";

const tooltipVariants = tv({
  base: "z-50 bg-bg-tooltip text-fg-onTooltip overflow-hidden rounded-md px-3 py-1.5 text-sm shadow-md animate-in fade-in-0 max-w-[200px] sm:max-w-[160px] duration-100 exiting:duration-75 exiting:animate-out exiting:fade-out-0",
});

interface TooltipProps
  extends TooltipRootProps,
    Omit<TooltipContentProps, "children">,
    VariantProps<typeof tooltipVariants> {
  content?: React.ReactNode;
  arrow?: boolean;
}
const Tooltip = ({
  delay,
  closeDelay,
  trigger,
  defaultOpen,
  isOpen,
  onOpenChange,
  isDisabled,
  content,
  arrow = true,
  children,
  ...props
}: TooltipProps) => {
  return (
    <TooltipRoot
      delay={delay}
      closeDelay={closeDelay}
      trigger={trigger}
      defaultOpen={defaultOpen}
      onOpenChange={onOpenChange}
      isOpen={isOpen}
      isDisabled={isDisabled}
    >
      {children}
      {arrow && <OverlayArrow />}
      <TooltipContent {...props}>{content}</TooltipContent>
    </TooltipRoot>
  );
};

type TooltipRootProps = AriaTooltipTriggerProps;
const TooltipRoot = ({ delay = 700, closeDelay = 0, ...props }: TooltipRootProps) => (
  <AriaTooltipTrigger delay={delay} closeDelay={closeDelay} {...props} />
);

interface TooltipContentProps
  extends Omit<AriaTooltipProps, "className">,
    VariantProps<typeof tooltipVariants> {
  className?: string;
}
const TooltipContent = ({ className, offset = 10, ...props }: TooltipContentProps) => {
  return <AriaTooltip offset={offset} className={tooltipVariants({ className })} {...props} />;
};

type OverlayArrowProps = Partial<React.SVGProps<SVGSVGElement>>;
const OverlayArrow = (props: OverlayArrowProps) => {
  return (
    <AriaOverlayArrow>
      <svg
        width={8}
        height={8}
        viewBox="0 0 8 8"
        className="fill-slate-700 stroke-gray-800 group-placement-left:-rotate-90 group-placement-right:rotate-90 group-placement-bottom:rotate-180 dark:fill-slate-600 dark:stroke-white/10 forced-colors:fill-[Canvas] forced-colors:stroke-[ButtonBorder]"
        {...props}
      >
        <path d="M0 0 L6 6 L12 0" />
      </svg>
    </AriaOverlayArrow>
  );
};

export { Tooltip, TooltipRoot, TooltipContent, OverlayArrow };
export type { TooltipProps, TooltipRootProps, TooltipContentProps };

Update the import paths to match your project setup.

Usage

  • Use tooltips to describe icons (e.g., icon buttons).
  • Keep the tooltip text minimal.
  • Never include tooltips on non-interactive components (div, span, p) because it will not be accessible for keyboard or screen reader users.
  • Don't place actions inside a tooltip

Options

Placement

TODO Fix Select

A tooltip is positioned in relation to its target. The default placement value is at the top.

"use client";

import React from "react";
import { Button } from "@/lib/components/core/default/button";
import { Item } from "@/lib/components/core/default/list-box";
import { Select } from "@/lib/components/core/default/select";
import { Tooltip, type TooltipProps } from "@/lib/components/core/default/tooltip";
import { PlusIcon } from "@/lib/icons";

type Placement = TooltipProps["placement"];

function Demo() {
  const [placement, setPlacement] = React.useState<Placement>("top");
  return (
    <div className="flex items-center gap-10">
      <Tooltip placement={placement} content="Add to library">
        <Button shape="square">
          <PlusIcon />
        </Button>
      </Tooltip>
      <Select
        label="Placement"
        selectedKey={placement}
        onSelectionChange={(key) => setPlacement(key as Placement)}
      >
        {[
          "bottom",
          "bottom left",
          "bottom right",
          "bottom start",
          "bottom end",
          "top",
          "top left",
          "top right",
          "top start",
          "top end",
          "left",
          "left top",
          "left",
          "bottom",
          "start",
          "start top",
          "start bottom",
          "right",
          "right top",
          "right",
          "bottom",
          "end",
          "end top",
          "end bottom",
        ].map((pos, index) => (
          <Item key={index}>{pos}</Item>
        ))}
      </Select>
    </div>
  );
}

Should flip

This option determines whether or not a tooltip should be able to switch sides when constrained by space.

"use client";

import React from "react";
import { Button } from "@/lib/components/core/default/button";
import { Switch } from "@/lib/components/core/default/switch";
import { Tooltip } from "@/lib/components/core/default/tooltip";
import { PlusIcon } from "@/lib/icons";

function Demo() {
  const [shouldFlip, setShouldFlip] = React.useState(false);
  return (
    <div className="flex flex-col items-center gap-10">
      <Tooltip shouldFlip={shouldFlip} content="Add to library">
        <Button shape="square">
          <PlusIcon />
        </Button>
      </Tooltip>
      <Switch isSelected={shouldFlip} onChange={(isSelected) => setShouldFlip(isSelected)}>
        Should flip
      </Switch>
    </div>
  );
}

Offset

The offset is the distance between the end of the tip and the target.

"use client";

import React from "react";
import { Button } from "@/lib/components/core/default/button";
import { NumberField } from "@/lib/components/core/default/number-field";
import { Tooltip } from "@/lib/components/core/default/tooltip";
import { PlusIcon } from "@/lib/icons";

function Demo() {
  const [offset, setOffset] = React.useState(10);
  return (
    <div className="flex flex-col items-center gap-10">
      <Tooltip offset={offset} content="Add to library">
        <Button shape="square">
          <PlusIcon />
        </Button>
      </Tooltip>
      <NumberField
        label="Offset"
        value={offset}
        onChange={(value) => setOffset(value)}
        className="max-w-[150px]"
      />
    </div>
  );
}

Container padding

To make sure that the tooltip will stay within certain boundaries (e.g., a browser window) it’s possible to define a container and a container padding value to respect. The default value for this is 12 px.

"use client";

import React from "react";
import { Button } from "@/lib/components/core/default/button";
import { NumberField } from "@/lib/components/core/default/number-field";
import { Tooltip } from "@/lib/components/core/default/tooltip";
import { PlusIcon } from "@/lib/icons";

function Demo() {
  const [containerPadding, setContainerPadding] = React.useState(12);
  return (
    <div className="flex flex-col items-center gap-10">
      <Tooltip containerPadding={containerPadding} content="Add to library">
        <Button shape="square">
          <PlusIcon />
        </Button>
      </Tooltip>
      <NumberField
        label="Container padding"
        value={containerPadding}
        onChange={(value) => setContainerPadding(value)}
        className="max-w-[150px]"
      />
    </div>
  );
}

Delay

The delay duration (the time from when the mouse enters a tooltip trigger until the tooltip opens) and the close delay can be customised.

"use client";

import React from "react";
import { Button } from "@/lib/components/core/default/button";
import { NumberField } from "@/lib/components/core/default/number-field";
import { Tooltip } from "@/lib/components/core/default/tooltip";
import { PlusIcon } from "@/lib/icons";

function Demo() {
  const [delay, setDelay] = React.useState(700);
  const [closeDelay, setCloseDelay] = React.useState(0);
  return (
    <div className="flex flex-col items-center gap-10">
      <Tooltip delay={delay} closeDelay={closeDelay} content="Add to library">
        <Button shape="square">
          <PlusIcon />
        </Button>
      </Tooltip>
      <div className="max-w-[150px] space-y-4">
        <NumberField label="Delay" value={delay} onChange={(value) => setDelay(value)} />
        <NumberField
          label="Close Delay"
          value={closeDelay}
          onChange={(value) => setCloseDelay(value)}
        />
      </div>
    </div>
  );
}

Examples

Custom content

The content of a tooltip can accept custom content, not just text.

import { Button } from "@/lib/components/core/default/button";
import { Tooltip } from "@/lib/components/core/default/tooltip";
import { PlusIcon } from "@/lib/icons";

function Demo() {
  return (
    <Tooltip
      content={
        <p>
          Add to <b className="font-bold">library</b>
        </p>
      }
    >
      <Button shape="square">
        <PlusIcon />
      </Button>
    </Tooltip>
  );
}

Composition

For more flexibility, you can use the composition pattern to create a custom tooltip.

import { Button } from "@/lib/components/core/default/button";
import { TooltipRoot, TooltipContent } from "@/lib/components/core/default/tooltip";

function Demo() {
  return (
    <TooltipRoot>
      <Button>Hover</Button>
      <TooltipContent>
        <p>Add to library</p>
      </TooltipContent>
    </TooltipRoot>
  );
}

API Reference

Tooltip

This component inherits and merges props from TooltipRoot and TooltipContent components.

PropTypeDefaultDescription
content
React.ReactNode
-
The content of the Tooltip

TooltipRoot

Contains all the parts of a tooltip.

PropTypeDefaultDescription
isDisabled
boolean
-
Whether the tooltip should be disabled, independent from the trigger.
delay
number
700
The delay time for the tooltip to show up.
closeDelay
number
0
The delay time for the tooltip to close.
trigger
'focus'
-
By default, opens for both focus and hover. Can be made to open only for focus.
isOpen
boolean
-
Whether the overlay is open by default (controlled).
defaultOpen
boolean
-
Whether the overlay is open by default (uncontrolled).
onOpenChange
(isOpen: boolean) => void
-
Handler that is called when the overlay's open state changes.

TooltipContent

The component that pops out when the tooltip is open.

PropTypeDefaultDescription
triggerRef
boolean
RefObject<Element>
The ref for the element which the tooltip positions itself with respect to.
When used within a TooltipTrigger this is set automatically. It is only required when used standalone.
isEntering
boolean
-
Whether the tooltip is currently performing an entry animation.
isExiting
boolean
-
Whether the tooltip is currently performing an exit animation.
placement
Placement
'top'
The placement of the element with respect to its anchor element.
containerPadding
number
12
The placement padding that should be applied between the element and its surrounding container.
offset
number
10
The additional offset applied along the main axis between the element and its anchor element.
crossOffset
number
0
The additional offset applied along the cross axis between the element and its anchor element.
shouldFlip
boolean
true
Whether the element should flip its orientation (e.g. top to bottom or left to right) when there is insufficient room for it to render completely.
isOpen
boolean
-
Whether the element is rendered.
defaultOpen
boolean
-
Whether the overlay is open by default (uncontrolled).
arrowBoundaryOffset
number
-
The minimum distance the arrow's edge should be from the edge of the overlay element.
children
boolean
ReactNode | (values: TooltipRenderProps & {defaultChildren: ReactNode | undefined}) => ReactNode
The children of the component. A function may be provided to alter the children based on component state.

Accessibility

Keyboard interactions

KeyDescription
Tab
Tabbing into an item that has a tooltip associated with it (e.g., a button icon) shows the tooltip.
Esc
Hides the tooltip

Built by mehdibha. The source code is available on GitHub.