Skip to content
+

Tooltip

Tooltips are visual-only floating elements that display information about a trigger element when a user hovers or focuses it.

Installation

Base UI components are all available as a single package.

npm install @base_ui/react

Once you have the package installed, import the component.

import * as Tooltip from '@base_ui/react/Tooltip';

Anatomy

Tooltip is implemented using a collection of related components:

  • <Tooltip.Provider /> wraps around <App /> or a group of <Tooltip.Root>s.
  • <Tooltip.Root /> is a top-level component that wraps the other components.
  • <Tooltip.Trigger /> renders the trigger element.
  • <Tooltip.Positioner /> renders the tooltip's positioning element.
  • <Tooltip.Popup /> renders the tooltip popup itself.
  • <Tooltip.Arrow /> renders an optional pointing arrow, placed inside the popup.
<Tooltip.Provider>
  <Tooltip.Root>
    <Tooltip.Trigger />
    <Tooltip.Positioner>
      <Tooltip.Popup>
        <Tooltip.Arrow />
      </Tooltip.Popup>
    </Tooltip.Positioner>
  </Tooltip.Root>
</Tooltip.Provider>

Provider

Tooltip.Provider provides a shared delay for tooltips so that once a tooltip is shown, the rest of the tooltips in the group don't wait for the delay before showing. You can wrap this globally, or around an individual group of tooltips anywhere in your React tree (or both).

<Tooltip.Provider>
  <App />
</Tooltip.Provider>
Press Enter to start editing

Accessibility

Tooltips are only for sighted users with access to a pointer with hover capability or keyboard focus. This means you must supply an accessible name via aria-label to trigger elements that don't contain text content, such as an icon button.

<Tooltip.Root>
  <Tooltip.Trigger aria-label="Edit">
    <EditIcon />
  </Tooltip.Trigger>
  <Tooltip.Positioner>
    <Tooltip.Popup>Edit</Tooltip.Popup>
  </Tooltip.Positioner>
</Tooltip.Root>

Your aria-label and tooltip content should closely match or be identical so that screen reader users and sighted users receive the same information.

Tooltips should ideally also be secondary in nature, because touch users cannot see them. They are most useful as progressive enhancement in high-density desktop applications that have many icon buttons where visual labels are impractical to use.

Placement

By default, the tooltip is placed on the top side of its trigger, the default anchor. To change this, use the side prop:

<Tooltip.Root>
  <Tooltip.Trigger />
  <Tooltip.Positioner side="right">
    <Tooltip.Popup>Tooltip</Tooltip.Popup>
  </Tooltip.Positioner>
</Tooltip.Root>

You can also change the alignment of the tooltip in relation to its anchor. By default, it is centered, but it can be aligned to an edge of the anchor using the alignment prop:

<Tooltip.Positioner side="right" alignment="start">
  <Tooltip.Popup>Tooltip</Tooltip.Popup>
</Tooltip.Positioner>

Due to collision detection, the tooltip may change its placement to avoid overflow. Therefore, your explicitly specified side and alignment props act as "ideal", or preferred, values.

To access the true rendered values, which may change as the result of a collision, the content element receives data attributes:

// Rendered HTML (simplified)
<div>
  <div data-side="left" data-alignment="end">
    Tooltip
  </div>
</div>

This allows you to conditionally style the tooltip based on its rendered side or alignment.

Offset

The sideOffset prop creates a gap between the anchor and tooltip popup, while alignmentOffset slides the tooltip popup from its alignment, acting logically for start and end alignments.

<Tooltip.Positioner sideOffset={10} alignmentOffset={10}>

Delay

To change how long the tooltip waits until it opens or closes, use the delay and closeDelay props, which represent how long the tooltip waits after the cursor rests on the trigger to open, or moves away from the trigger to close, in milliseconds:

<Tooltip.Root delay={200} closeDelay={200}>

The delay type can be changed from "rest" (user's cursor is static over the trigger for the given timeout in milliseconds) to "hover" (the user's cursor has entered the trigger):

<Tooltip.Root delayType="hover">

Controlled

To control the tooltip with external state, use the open and onOpenChange props:

function App() {
  const [open, setOpen] = React.useState(false);
  return (
    <Tooltip.Root open={open} onOpenChange={setOpen}>
      {/* Subcomponents */}
    </Tooltip.Root>
  );
}

Arrow

To add an arrow (caret or triangle) inside the tooltip content that points toward the center of the anchor element, use the Tooltip.Arrow component:

<Tooltip.Positioner>
  <Tooltip.Popup>
    Tooltip
    <Tooltip.Arrow />
  </Tooltip.Popup>
</Tooltip.Positioner>

It automatically positions a wrapper element that can be styled or contain a custom SVG shape.

Cursor following

The tooltip can follow the cursor on both axes or one axis using the followCursorAxis prop on Tooltip.Root. Possible values are: none (default), both, x, or y.

Press Enter to start editing

Anchoring

By default, the Trigger acts as the anchor, but this can be changed to another element.

  • A DOM element (stored in React state):
<Tooltip.Positioner anchor={anchorNode}>
  • A React ref:
<Tooltip.Positioner anchor={anchorRef}>
  • A virtual element object, consisting of a getBoundingClientRect method and an optional contextElement property:
<Tooltip.Positioner
  anchor={{
    getBoundingClientRect: () => DOMRect,
    // `contextElement` is an optional but recommended property when `getBoundingClientRect` is
    // derived from a real element, to ensure collision detection and position updates work as
    // expected in certain DOM trees.
    contextElement: domNode,
  }}
>

Styling

The Tooltip.Positioner element receives the following CSS variables, which can be used by Tooltip.Popup:

  • --anchor-width: Specifies the width of the anchor element. You can use this to match the width of the tooltip with its anchor.
  • --anchor-height: Specifies the height of the anchor element. You can use this to match the height of the tooltip with its anchor.
  • --available-width: Specifies the available width of the popup element before it overflows the viewport.
  • --available-height: Specifies the available height of the popup element before it overflows the viewport.
  • --transform-origin: Specifies the origin of the popup element that represents the point of the anchor element's center. When animating scale, this allows it to correctly emanate from the center of the anchor.

By default, maxWidth and maxHeight are already specified on the positioner using --available-{width,height} to prevent the tooltip from being too big to fit on the screen.

Animations

The tooltip can animate when opening or closing with either:

  • CSS transitions
  • CSS animations
  • JavaScript animations

CSS transitions

Here is an example of how to apply a symmetric scale and fade transition with the default conditionally-rendered behavior:

<Tooltip.Popup className="TooltipPopup">Tooltip</Tooltip.Popup>
.TooltipPopup {
  transform-origin: var(--transform-origin);
  transition-property: opacity, transform;
  transition-duration: 0.2s;
  /* Represents the final styles once exited */
  opacity: 0;
  transform: scale(0.9);
}

/* Represents the final styles once entered */
.TooltipPopup[data-state='open'] {
  opacity: 1;
  transform: scale(1);
}

/* Represents the initial styles when entering */
.TooltipPopup[data-entering] {
  opacity: 0;
  transform: scale(0.9);
}

Styles need to be applied in three states:

  • The exiting styles, placed on the base element class
  • The open styles, placed on the base element class with [data-state="open"]
  • The entering styles, placed on the base element class with [data-entering]

In newer browsers, there is a feature called @starting-style which allows transitions to occur on open for conditionally-mounted components:

/* Base UI API - Polyfill */
.TooltipPopup[data-entering] {
  opacity: 0;
  transform: scale(0.9);
}

/* Official Browser API - no Firefox support as of May 2024 */
@starting-style {
  .TooltipPopup[data-state='open'] {
    opacity: 0;
    transform: scale(0.9);
  }
}

CSS animations

CSS animations can also be used, requiring only two separate declarations:

@keyframes scale-in {
  from {
    opacity: 0;
    transform: scale(0.9);
  }
}

@keyframes scale-out {
  to {
    opacity: 0;
    transform: scale(0.9);
  }
}

.TooltipPopup {
  animation: scale-in 0.2s forwards;
}

.TooltipPopup[data-exiting] {
  animation: scale-out 0.2s forwards;
}

JavaScript animations

The keepMounted prop lets an external library control the mounting, for example framer-motion's AnimatePresence component.

function App() {
  const [open, setOpen] = useState(false);
  return (
    <Tooltip.Root open={open} onOpenChange={setOpen}>
      <Tooltip.Trigger>Trigger</Tooltip.Trigger>
      <AnimatePresence>
        {open && (
          <Tooltip.Positioner keepMounted>
            <Tooltip.Popup
              render={
                <motion.div
                  initial={{ opacity: 0 }}
                  animate={{ opacity: 1 }}
                  exit={{ opacity: 0 }}
                />
              }
            >
              Tooltip
            </Tooltip.Popup>
          </Tooltip.Positioner>
        )}
      </AnimatePresence>
    </Tooltip.Root>
  );
}

Animation states

Four states are available as data attributes to animate the popup, which enables full control depending on whether the popup is being animated with CSS transitions or animations, JavaScript, or is using the keepMounted prop.

  • [data-state="open"] - open state is true.
  • [data-state="closed"] - open state is false. Can still be mounted to the DOM if closing.
  • [data-entering] - the popup was just inserted to the DOM. The attribute is removed 1 animation frame later. Enables "starting styles" upon insertion for conditional rendering.
  • [data-exiting] - the popup is in the process of being removed from the DOM, but is still mounted.

Instant animation

Animations can be removed under certain conditions using the data-instant attribute on Tooltip.Popup. This attribute can be used unconditionally, but it also has different values for granular checks:

  • data-instant="delay" indicates the tooltip is grouped and instantly opened with no delay.
  • data-instant="focus" indicates it was triggered by keyboard focus.
  • data-instant="dismiss" indicates it was dismissed by pressing the esc key.

In most of these cases, you'll want to remove any animations:

.TooltipPopup[data-instant] {
  transition-duration: 0s;
}

Overriding default components

Use the render prop to override the rendered elements with your own components.

// Element shorthand
<Tooltip.Popup render={<MyTooltipPopup />} />
// Function
<Tooltip.Popup render={(props) => <MyTooltipPopup {...props} />} />