moduix

Tooltip

A popup that appears when an element is hovered or focused.

API Reference

Original primitive API

Behavior, accessibility details, and low-level props are documented by Base UI.

Base UI API

Basic

Use a tooltip for short visual labels on controls. Always give the trigger its own accessible name: tooltips are not a replacement for aria-label or visible text.

import {  BellIcon,  Button,  Tooltip,  TooltipContent,  TooltipTrigger,} from "moduix";import styles from "./tooltip-demo.module.css";export function TooltipDemo() {  return (    <Tooltip>      <TooltipTrigger render={<Button />} aria-label="Notifications">        <span className={styles.triggerContent}>          <BellIcon className={styles.icon} />          Notifications        </span>      </TooltipTrigger>      <TooltipContent>Notifications</TooltipContent>    </Tooltip>  );}
.triggerContent {  display: inline-flex;  align-items: center;  gap: var(--spacing-2);}.icon {  width: 1rem;  height: 1rem;}

Full list of component variables available for project-level overrides.

PropertyDefaultDescription
--tooltip-arrow-height0.625remControls the default arrow SVG height.
--tooltip-arrow-inline-offset13pxControls the inline-axis arrow offset.
--tooltip-arrow-size8pxControls the block-axis arrow offset.
--tooltip-arrow-stroke-colorvar(--tooltip-border-color)Controls arrow border color.
--tooltip-arrow-width1.25remControls the default arrow SVG width.
--tooltip-bgvar(--color-popover)Controls the popup background color.
--tooltip-border-colorvar(--color-border)Controls the popup border color.
--tooltip-border-widthvar(--border-width-sm)Controls popup border width.
--tooltip-colorvar(--color-popover-foreground)Controls the popup text color.
--tooltip-content-transition150msControls content transitions between triggers.
--tooltip-disabled-opacityvar(--opacity-disabled)Controls disabled trigger opacity.
--tooltip-focus-ring-colorvar(--color-ring)Controls trigger focus ring color.
--tooltip-focus-ring-offset-1pxControls trigger focus ring offset.
--tooltip-focus-ring-widthvar(--border-width-sm)Controls trigger focus ring width.
--tooltip-font-sizevar(--text-sm)Controls the popup font size.
--tooltip-line-heightvar(--line-height-text-sm)Controls the popup line height.
--tooltip-max-height24remControls the popup max height.
--tooltip-max-width20remControls the popup max width.
--tooltip-padding-x0.5remControls the popup horizontal padding.
--tooltip-padding-y0.25remControls the popup vertical padding.
--tooltip-radiusvar(--radius-md)Controls the popup border radius.
--tooltip-scalevar(--scale-popup)Controls the popup enter and exit scale.
--tooltip-shadowvar(--shadow-lg)Controls the popup shadow.
--tooltip-transition150msControls popup and trigger transitions.
--tooltip-trigger-bgvar(--color-background)Controls trigger background color.
--tooltip-trigger-bg-activevar(--tooltip-trigger-bg-hover, var(--color-accent))Controls trigger background while the tooltip is open.
--tooltip-trigger-bg-hovervar(--color-accent)Controls trigger hover background.
--tooltip-trigger-border-colorvar(--color-border)Controls trigger border color.
--tooltip-trigger-border-widthvar(--border-width-sm)Controls trigger border width.
--tooltip-trigger-colorvar(--color-foreground)Controls trigger text color.
--tooltip-trigger-font-sizevar(--text-sm)Controls trigger font size.
--tooltip-trigger-ghost-sizevar(--size-lg)Controls icon-only trigger size.
--tooltip-trigger-heightvar(--size-lg)Controls trigger height.
--tooltip-trigger-line-heightvar(--line-height-text-sm)Controls trigger line height.
--tooltip-trigger-padding-x0.875remControls trigger horizontal padding.
--tooltip-trigger-padding-y0.5remControls trigger vertical padding.
--tooltip-trigger-radiusvar(--radius-md)Controls trigger border radius.
--tooltip-widthmax-contentControls the popup width.

Interactive variables scoped for docs preview without changing size scale tokens.

PropertyValueDefaultDescription
--tooltip-bgvar(--color-popover)Controls popup background color.
--tooltip-border-colorvar(--color-border)Controls popup border color.
--tooltip-colorvar(--color-popover-foreground)Controls popup text color.
--tooltip-font-sizevar(--text-sm)Controls popup font size.
--tooltip-focus-ring-colorvar(--color-ring)Controls trigger focus ring color.
--tooltip-radiusvar(--radius-md)Controls popup border radius.
--tooltip-shadowvar(--shadow-lg)Controls popup shadow.
--tooltip-trigger-bgvar(--color-background)Controls trigger background color.
--tooltip-trigger-bg-hovervar(--color-accent)Controls trigger hover background.
--tooltip-trigger-colorvar(--color-foreground)Controls trigger text color.

Anatomy

Tooltip is composed of one trigger and one popup layer managed by Tooltip. Keep each TooltipTrigger connected to its matching Tooltip root so focus, hover, and delay behavior stay predictable.

Tooltip
├─ TooltipTrigger
└─ TooltipContent
   └─ portal
      └─ positioner
         └─ popup
            ├─ arrow
            └─ viewport
               └─ content
<Tooltip>
  <TooltipTrigger aria-label="Notifications">Hover or focus</TooltipTrigger>
  <TooltipContent>Notifications</TooltipContent>
</Tooltip>
PartRole
TooltipRoot state and timing behavior. Handles open state, delay, controlled props, and optional handle linking.
TooltipTriggerInteractive anchor element. Opens the tooltip on hover/focus and should keep its own accessible name.
TooltipContentComposed popup layer. Renders portal, positioner, popup, optional arrow, and viewport automatically.

In most cases, keep the default composition and style only visible parts with className. Use classNames, container, and the focused placement props on TooltipContent for common customization. Reach for the slot prop escape hatches only when you need to pass non-class props to the internal service slots.

Composition

Use Tooltip for root state and behavior props such as open, defaultOpen, onOpenChange, and handle.

className styles the visible popup. classNames styles the internal service slots that are hidden from the default composition. Use container, withArrow={false} to hide the arrow, arrow to replace its graphic, and placement props such as side or sideOffset for positioning. Use slotProps only when you need to pass non-class props to the internal Base UI service slots:

<TooltipContent
  className={styles.popup}
  classNames={{
    portal: styles.portal,
    positioner: styles.positioner,
    arrow: styles.arrow,
    viewport: styles.viewport,
  }}
  slotProps={{
    portal: { keepMounted: true },
    positioner: { collisionPadding: 8 },
    arrow: { style: { transformOrigin: 'center' } },
  }}
/>

Examples

Toolbar

Wrap related tooltips in one TooltipProvider to share delay behavior across a toolbar.

import {  InfoIcon,  PlusIcon,  ShareIcon,  Tooltip,  TooltipContent,  TooltipProvider,  TooltipTrigger,} from "moduix";import styles from "./tooltip-demo.module.css";export function ToolbarTooltipDemo() {  return (    <TooltipProvider delay={300}>      <div className={styles.toolbar}>        <Tooltip>          <TooltipTrigger aria-label="Add item" data-variant="ghost">            <PlusIcon className={styles.icon} />          </TooltipTrigger>          <TooltipContent sideOffset={16}>Add item</TooltipContent>        </Tooltip>        <Tooltip>          <TooltipTrigger aria-label="Share" data-variant="ghost">            <ShareIcon className={styles.icon} />          </TooltipTrigger>          <TooltipContent sideOffset={16}>Share</TooltipContent>        </Tooltip>        <Tooltip>          <TooltipTrigger aria-label="Details" data-variant="ghost">            <InfoIcon className={styles.icon} />          </TooltipTrigger>          <TooltipContent sideOffset={16}>Details</TooltipContent>        </Tooltip>      </div>    </TooltipProvider>  );}
.icon {  width: 1rem;  height: 1rem;}.toolbar {  display: inline-flex;  align-items: center;  gap: var(--border-width-sm);  padding: var(--spacing-1);  border: var(--border-width-sm) solid var(--color-border);  border-radius: var(--radius-lg);  background: var(--color-muted);}

Without Arrow

Set arrow to false when the popup should read as a compact floating label.

import {  Button,  Tooltip,  TooltipContent,  TooltipTrigger,} from "moduix";export function TooltipWithoutArrowDemo() {  return (    <Tooltip>      <TooltipTrigger render={<Button />} aria-label="Tooltip without arrow">        Hover or focus      </TooltipTrigger>      <TooltipContent withArrow={false}>Tooltip without arrow</TooltipContent>    </Tooltip>  );}

Side Control

Pass placement props directly to TooltipContent for the common positioning cases.

import {  Button,  Tooltip,  TooltipContent,  TooltipTrigger,} from "moduix";import { useState } from "react";import styles from "./tooltip-demo.module.css";type TooltipSide = "top" | "right" | "bottom" | "left";export function SideControlTooltip() {  const [side, setSide] = useState("top" as TooltipSide);  return (    <div className={styles.stack}>      <div className={styles.sideButtons}>        {tooltipSides.map((item) => (          <button            key={item}            type="button"            className={styles.sideButton}            data-active={item === side || undefined}            onClick={() => setSide(item)}          >            {item}          </button>        ))}      </div>      <Tooltip>        <TooltipTrigger render={<Button />} aria-label={`Tooltip side: ${side}`}>          Hover or focus        </TooltipTrigger>        <TooltipContent side={side}>Side: {side}</TooltipContent>      </Tooltip>    </div>  );}
.stack {  display: flex;  flex-direction: column;  align-items: center;  gap: var(--spacing-3);}.sideButtons {  display: inline-flex;  align-items: center;  flex-wrap: wrap;  gap: var(--spacing-1);  padding: var(--spacing-1);  border: var(--border-width-sm) solid var(--color-border);  border-radius: var(--radius-md);  background: var(--color-muted);}.sideButton {  min-width: 4.75rem;  min-height: 2rem;  padding: 0 var(--spacing-3);  border: var(--border-width-sm) solid var(--color-border);  border-radius: var(--radius-sm);  background: var(--color-background);  color: var(--color-foreground);  font: inherit;  font-size: var(--text-sm);  line-height: var(--line-height-text-sm);  font-weight: var(--weight-medium);  text-transform: capitalize;  cursor: pointer;  transition:    background-color var(--transition-default),    color var(--transition-default);  &[data-active] {    background: var(--color-primary);    color: var(--color-primary-foreground);  }}
const tooltipSides = ["top", "right", "bottom", "left"] as TooltipSide[];

Detached Trigger

Use createTooltipHandle when the trigger and tooltip content cannot live in the same tree.

import {  Button,  Tooltip,  TooltipContent,  TooltipTrigger,  createTooltipHandle,} from "moduix";import { useMemo } from "react";export function DetachedTriggerTooltip() {  const tooltipHandle = useMemo(() => createTooltipHandle(), []);  return (    <>      <TooltipTrigger        handle={tooltipHandle}        render={<Button />}        aria-label="Detached tooltip"      >        Detached trigger      </TooltipTrigger>      <Tooltip handle={tooltipHandle}>        <TooltipContent>Linked with handle.</TooltipContent>      </Tooltip>    </>  );}

Multiple Triggers

Share one tooltip between several triggers by linking them with the same handle and rendering the active trigger payload.

import {  InfoIcon,  PlusIcon,  ShareIcon,  Tooltip,  TooltipContent,  TooltipProvider,  TooltipTrigger,  createTooltipHandle,} from "moduix";import { useMemo } from "react";import styles from "./tooltip-demo.module.css";export function MultipleTriggersTooltip() {  const tooltipHandle = useMemo(    () => createTooltipHandle<{ text: string }>(),    [],  );  return (    <TooltipProvider delay={250}>      <div className={styles.row}>        <TooltipTrigger          aria-label="Create"          handle={tooltipHandle}          payload={{ text: "Create" }}          data-variant="ghost"        >          <PlusIcon className={styles.icon} />        </TooltipTrigger>        <TooltipTrigger          aria-label="Share"          handle={tooltipHandle}          payload={{ text: "Share" }}          data-variant="ghost"        >          <ShareIcon className={styles.icon} />        </TooltipTrigger>        <TooltipTrigger          aria-label="Details"          handle={tooltipHandle}          payload={{ text: "Details" }}          data-variant="ghost"        >          <InfoIcon className={styles.icon} />        </TooltipTrigger>        <Tooltip handle={tooltipHandle}>          {({ payload }) => <TooltipContent>{payload?.text}</TooltipContent>}        </Tooltip>      </div>    </TooltipProvider>  );}
.icon {  width: 1rem;  height: 1rem;}.row {  display: flex;  align-items: center;  justify-content: center;  flex-wrap: wrap;  gap: var(--spacing-2);}

Custom Styles

Use className for the popup itself, classNames for internal slots such as the arrow, and CSS variables for broad theme changes. Portal, positioner, and viewport are rendered automatically.

import {  Tooltip,  TooltipContent,  TooltipTrigger,} from "moduix";import styles from "./tooltip-demo.module.css";export function CustomStylesTooltip() {  return (    <Tooltip>      <TooltipTrigger        aria-label="Custom styled tooltip"        className={styles.customTrigger}      >        Custom style      </TooltipTrigger>      <TooltipContent        className={styles.customPopup}        classNames={{          portal: styles.customPortal,          positioner: styles.customPositioner,          arrow: styles.customArrow,          viewport: styles.customViewport,        }}      >        Styled through className      </TooltipContent>    </Tooltip>  );}
.customPopup {  --tooltip-bg: var(--color-primary);  --tooltip-color: var(--color-primary-foreground);  --tooltip-border-color: color-mix(in oklab, var(--color-primary), black 18%);  --tooltip-arrow-stroke-color: var(--tooltip-border-color);  --tooltip-padding-x: var(--spacing-3);  --tooltip-padding-y: var(--spacing-2);}.customPortal {  z-index: var(--z-popup);}.customPositioner {  filter: drop-shadow(0 0.5rem 1rem rgb(0 0 0 / 0.14));}.customViewport {  min-width: 10rem;  text-align: left;}.customTrigger {  --tooltip-trigger-bg: var(--color-primary);  --tooltip-trigger-bg-hover: color-mix(in oklab, var(--color-primary), white 10%);  --tooltip-trigger-bg-active: color-mix(in oklab, var(--color-primary), black 12%);  --tooltip-trigger-border-color: color-mix(in oklab, var(--color-primary), black 18%);  --tooltip-trigger-color: var(--color-primary-foreground);}.customArrow {  --tooltip-arrow-width: 1.5rem;  --tooltip-arrow-height: 0.75rem;}

On this page