Skip to main content
react-md

Tooltip

Tooltips display informative text when users hover over, focus on, or tap an element.

Simple Example

A tooltip can be created by using the Tooltip component and the useTooltip hook to handle controlling the visibility. The default behavior will be to show the tooltip after hovering/focusing/touching for 1 second.

"use client";

import { Button } from "@react-md/core/button/Button";
import { Tooltip } from "@react-md/core/tooltip/Tooltip";
import { useTooltip } from "@react-md/core/tooltip/useTooltip";
import { type ReactElement } from "react";

export default function SimpleTooltipExample(): ReactElement {
  const { elementProps, tooltipProps } = useTooltip();

  return (
    <>
      <Button {...elementProps}>Button</Button>
      <Tooltip {...tooltipProps}>Tooltip</Tooltip>
    </>
  );
}

Press Enter to start editing.

Dense Tooltip

The tooltip can be rendered with a smaller size and spacing from the tooltipped element by enabling the dense option.

"use client";

import { Button } from "@react-md/core/button/Button";
import { Tooltip } from "@react-md/core/tooltip/Tooltip";
import { useTooltip } from "@react-md/core/tooltip/useTooltip";
import { type ReactElement } from "react";

export default function DenseTooltipExample(): ReactElement {
  const { elementProps, tooltipProps } = useTooltip({
    dense: true,
  });

  return (
    <>
      <Button {...elementProps}>Button</Button>
      <Tooltip {...tooltipProps}>Tooltip</Tooltip>
    </>
  );
}

Press Enter to start editing.

Long Text Tooltip

Tooltips have a max-width set to 15rem by default but can be configured by setting the core.$tooltip-max-width Sass variable or through CSS on the tooltip.

"use client";

import { Button } from "@react-md/core/button/Button";
import { Tooltip } from "@react-md/core/tooltip/Tooltip";
import { useTooltip } from "@react-md/core/tooltip/useTooltip";
import { type ReactElement } from "react";

export default function LongTextTooltipExample(): ReactElement {
  const { elementProps, tooltipProps } = useTooltip();

  return (
    <>
      <Button {...elementProps}>Button</Button>
      <Tooltip {...tooltipProps}>
        Nam laoreet, felis ut commodo tristique, dui lorem iaculis metus, vitae
        pharetra ipsum nulla sed mauris. Suspendisse ultrices vel dui id
        posuere. Aenean pellentesque urna ac nisi elementum fringilla. Sed quis
        vestibulum ex, in auctor lorem. Morbi a elit viverra, dignissim leo at,
        accumsan ligula. Aliquam velit ligula, molestie a lorem ut, commodo
        hendrerit est. Sed lobortis luctus orci quis ultricies. Nullam luctus
        urna quis libero aliquet, non tincidunt augue sagittis. Duis eleifend
        ultricies fermentum. Nulla volutpat tempor est, eget hendrerit nisi
        sodales vitae.
      </Tooltip>
    </>
  );
}

Press Enter to start editing.

Tooltip Positioning

The Tooltip will automatically attempt to render itself within the viewport either above or below the tooltipped element preferring below. The positioning can be configured by providing the defaultPosition option to the useTooltip hook as one of the following:

"use client";

import { Button } from "@react-md/core/button/Button";
import { Tooltip } from "@react-md/core/tooltip/Tooltip";
import { useTooltip } from "@react-md/core/tooltip/useTooltip";
import { type ReactElement } from "react";

export default function TooltipPositioningExample(): ReactElement {
  const { elementProps, tooltipProps } = useTooltip({
    // this is the default
    defaultPosition: "below",
    // defaultPosition: "above",
    // defaultPosition: "left",
    // defaultPosition: "right",
  });

  return (
    <>
      <Button {...elementProps}>Button</Button>
      <Tooltip {...tooltipProps}>Tooltip</Tooltip>
    </>
  );
}

Press Enter to start editing.

Forced Tooltip Position

A specific position can be forced by setting the position option instead.

"use client";

import { Button } from "@react-md/core/button/Button";
import { Tooltip } from "@react-md/core/tooltip/Tooltip";
import { useTooltip } from "@react-md/core/tooltip/useTooltip";
import { type ReactElement } from "react";

export default function ForcedTooltipPositionExample(): ReactElement {
  const { elementProps, tooltipProps } = useTooltip({
    // this is the default
    position: "below",
    // position: "above",
    // position: "left",
    // position: "right",
  });

  return (
    <>
      <Button {...elementProps}>Button</Button>
      <Tooltip {...tooltipProps}>Tooltip</Tooltip>
    </>
  );
}

Press Enter to start editing.

Tooltipped Button

Since Tooltips are normally used with Buttons, a simple helper component is available to automatically render a tooltip when a tooltip prop is provided. The TooltippedButton also defaults the buttonType to "icon" instead of "text" like normal buttons.

import { TooltippedButton } from "@react-md/core/button/TooltippedButton";
import CloseIcon from "@react-md/material-icons/CloseIcon";
import FavoriteIcon from "@react-md/material-icons/FavoriteIcon";
import { type ReactElement } from "react";

export default function TooltippedButtonExample(): ReactElement {
  return (
    <>
      <TooltippedButton aria-label="No tooltip">
        <CloseIcon />
      </TooltippedButton>
      <TooltippedButton
        aria-label="Favorite"
        tooltip="Tooltip"
        themeType="outline"
      >
        <FavoriteIcon />
      </TooltippedButton>
      <TooltippedButton
        aria-label="Favorite"
        tooltip={
          <span>
            <strong>Strong</strong> tooltip
          </span>
        }
        theme="success"
      >
        <FavoriteIcon />
      </TooltippedButton>
    </>
  );
}

Press Enter to start editing.

Enabling Hover Mode

Tooltips can also be updated to have a "hover mode" so that subsequent tooltips are shown immediately instead of requiring the default delay. After no tooltips have been shown via mouse for a few seconds, the "hover mode" will be disabled and the initial hover delay will be used again. This feature can be enabled for all tooltips in the application or just a small group of tooltips by wrapping the components in the TooltipHoverModeProvider.

"use client";

import { TooltippedButton } from "@react-md/core/button/TooltippedButton";
import { TooltipHoverModeProvider } from "@react-md/core/tooltip/TooltipHoverModeProvider";
import { type ReactElement } from "react";

export default function EnablingHoverModeExample(): ReactElement {
  return (
    <TooltipHoverModeProvider>
      {Array.from({ length: 5 }, (_, i) => (
        <TooltippedButton
          key={i}
          tooltip={`Tooltip ${i + 1}`}
          buttonType="text"
        >
          Button {i + 1}
        </TooltippedButton>
      ))}
    </TooltipHoverModeProvider>
  );
}

Press Enter to start editing.

Configuring Tooltip Timeouts

The timeouts for showing and hiding the tooltips can be configured globally using the TooltipHoverModeProvider or a tooltip-by-tooltip basis with the useTooltip hook which would override the TooltipHoverModeProvider value.

"use client";

import { TooltippedButton } from "@react-md/core/button/TooltippedButton";
import { TooltipHoverModeProvider } from "@react-md/core/tooltip/TooltipHoverModeProvider";
import { type ReactElement } from "react";

export default function ConfiguringTooltipTimeoutsExample(): ReactElement {
  return (
    <TooltipHoverModeProvider
      hoverTimeout={500}
      leaveTimeout={300}
      disableTimeout={10000}
    >
      {Array.from({ length: 5 }, (_, i) => (
        <TooltippedButton
          key={i}
          tooltip={`Tooltip ${i + 1}`}
          buttonType="text"
        >
          {`Button ${i + 1}`}
        </TooltippedButton>
      ))}
      <TooltippedButton
        tooltip="Another tooltip"
        // these are pass-through to the `useTooltip` hook
        // i.e.
        // useTooltip({ ...tooltipOptions, disabled: !tooltip || tooltipOptions?.disabled })
        tooltipOptions={{
          leaveTimeout: 1000,
          hoverTimeout: 1500,
        }}
        buttonType="text"
      >
        Custom Timeout
      </TooltippedButton>
    </TooltipHoverModeProvider>
  );
}

Press Enter to start editing.

Overflow Only Tooltip

The useTooltip hook also supports an overflowOnly option that will display the tooltip only while there is overflown content for the tooltipped element.

No Overflow.
There will be overflow here.
"use client";

import { Box } from "@react-md/core/box/Box";
import { cssUtils } from "@react-md/core/cssUtils";
import { Tooltip } from "@react-md/core/tooltip/Tooltip";
import { useTooltip } from "@react-md/core/tooltip/useTooltip";
import { type CSSProperties, type ReactElement } from "react";

const style: CSSProperties = {
  width: "8rem",
};

export default function OverflowOnlyTooltipExample(): ReactElement {
  const { elementProps, tooltipProps } = useTooltip<HTMLDivElement>({
    overflowOnly: true,
  });
  return (
    <Box stacked>
      <div
        {...elementProps}
        style={style}
        className={cssUtils({ textOverflow: "ellipsis" })}
      >
        No Overflow.
      </div>
      <div
        {...elementProps}
        style={style}
        className={cssUtils({ textOverflow: "ellipsis" })}
      >
        There will be overflow here.
      </div>
      <Tooltip {...tooltipProps}>Tooltip!</Tooltip>
    </Box>
  );
}

Press Enter to start editing.

Custom Overflow Element

If the tooltipped element is a flex or grid container, a child element will most likely need to be updated to add the overflow styles. This would cause the tooltip to never appear since the tooltipped element is not considered overflown even though the child is truncated. In this case, add the overflowRef to the truncated element and the tooltip will correctly appear.

Try commenting out the ref or changing the width so there is no truncated text in this example and hover the button.

"use client";

import { Button } from "@react-md/core/button/Button";
import { cssUtils } from "@react-md/core/cssUtils";
import { Tooltip } from "@react-md/core/tooltip/Tooltip";
import { useTooltip } from "@react-md/core/tooltip/useTooltip";
import { type ReactElement } from "react";

export default function CustomOverflowElementExample(): ReactElement {
  const { elementProps, tooltipProps, overflowRef } = useTooltip({
    overflowOnly: true,
  });

  return (
    <>
      <Button {...elementProps} style={{ width: "2rem" }}>
        <span
          ref={overflowRef}
          className={cssUtils({ textOverflow: "ellipsis" })}
        >
          Some long content
        </span>
      </Button>
      <Tooltip {...tooltipProps}>Tooltip!</Tooltip>
    </>
  );
}

Press Enter to start editing.

Custom Tooltip

A Tooltip can be rendered without the useTooltip hook but will require some additional styling for the correct positioning to work.

  1. Enable the disablePortal prop so it renders inline
  2. Add styles for position: absolute positioning
  3. Wrap in a container element with position: relative
  4. Optionally set the textOverflow prop to "nowrap" so that the width is not restricted to the wrapper element's width
Always visible tooltip
import { cssUtils } from "@react-md/core/cssUtils";
import { Tooltip } from "@react-md/core/tooltip/Tooltip";
import { type ReactElement } from "react";

import styles from "./CustomTooltipExample.module.scss";

export default function CustomTooltipExample(): ReactElement {
  return (
    <div
      className={cssUtils({
        backgroundColor: "success",
        className: styles.container,
      })}
    >
      <Tooltip
        visible
        className={styles.tooltip}
        disablePortal
        textOverflow="nowrap"
      >
        Always visible tooltip
      </Tooltip>
    </div>
  );
}

Press Enter to start editing.

@use "everything";

.container {
  height: 3em;
  position: relative;
  width: 3em;
}

.tooltip {
  left: 50%;
  position: absolute;
  top: calc(100% + everything.tooltip-get-var(spacing));
  transform: translateX(-50%);
}

Press Enter to start editing.

Progressbar Tooltip Example

This demo just showcases a possible use-case for the custom tooltip by rendering it with a progressbar to show the current progress.

0%
"use client";

import { box } from "@react-md/core/box/styles";
import { Button } from "@react-md/core/button/Button";
import { cssUtils } from "@react-md/core/cssUtils";
import { LinearProgress } from "@react-md/core/progress/LinearProgress";
import { Tooltip } from "@react-md/core/tooltip/Tooltip";
import { useToggle } from "@react-md/core/useToggle";
import { loop } from "@react-md/core/utils/loop";
import CelebrationOutlinedIcon from "@react-md/material-icons/CelebrationOutlinedIcon";
import { type ReactElement, useEffect, useState } from "react";

import styles from "./ProgressbarTooltipExample.module.scss";

export default function ProgressbarTooltipExample(): ReactElement {
  const { value, toggle, toggled } = useIncrementingValue();

  return (
    <>
      <Button onClick={toggle} theme="primary" themeType="contained">
        {toggled ? "Stop" : "Start"}
      </Button>
      <div style={{ "--offset": `${value}%` }} className={styles.container}>
        <LinearProgress aria-label="Example" value={value} />
        <Tooltip
          visible
          className={box({
            disableWrap: true,
            disablePadding: true,
            className: cssUtils({
              backgroundColor:
                value < 30 ? "warning" : value === 100 ? "success" : undefined,
              className: styles.tooltip,
            }),
          })}
          disablePortal
          textOverflow="nowrap"
        >
          {value}%
          {value === 100 && <CelebrationOutlinedIcon theme="on-success" />}
        </Tooltip>
      </div>
    </>
  );
}

function useIncrementingValue(): {
  value: number;
  toggle: () => void;
  toggled: boolean;
} {
  const { toggled, toggle } = useToggle();
  const [value, setValue] = useState(0);
  useEffect(() => {
    if (!toggled) {
      return;
    }

    const timeout = window.setTimeout(
      () => {
        setValue(
          loop({
            value,
            min: 0,
            max: 100,
            increment: true,
          }),
        );
      },
      value === 100 ? 5000 : 300,
    );
    return () => {
      window.clearTimeout(timeout);
    };
  }, [toggled, value]);

  return { value, toggle, toggled };
}

declare module "react" {
  interface CSSProperties {
    "--offset"?: string;
  }
}

Press Enter to start editing.

@use "everything";

.container {
  position: relative;
  width: 100%;
}

.tooltip {
  left: var(--offset, 0%);
  position: absolute;
  top: calc(100% + 1em);
  transform: translateX(-50%);
  transition:
    left everything.$linear-duration everything.$linear-timing-function,
    background-color 0.3s;
  will-change: left, right;

  @include everything.rtl {
    left: auto;
    right: var(--offset, 0%);
  }
}

Press Enter to start editing.

Accessibility

Tooltips follow the tooltip pattern and the tooltip role.