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.
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.
| Property | Default | Description |
|---|---|---|
| --tooltip-arrow-height | 0.625rem | Controls the default arrow SVG height. |
| --tooltip-arrow-inline-offset | 13px | Controls the inline-axis arrow offset. |
| --tooltip-arrow-size | 8px | Controls the block-axis arrow offset. |
| --tooltip-arrow-stroke-color | var(--tooltip-border-color) | Controls arrow border color. |
| --tooltip-arrow-width | 1.25rem | Controls the default arrow SVG width. |
| --tooltip-bg | var(--color-popover) | Controls the popup background color. |
| --tooltip-border-color | var(--color-border) | Controls the popup border color. |
| --tooltip-border-width | var(--border-width-sm) | Controls popup border width. |
| --tooltip-color | var(--color-popover-foreground) | Controls the popup text color. |
| --tooltip-content-transition | 150ms | Controls content transitions between triggers. |
| --tooltip-disabled-opacity | var(--opacity-disabled) | Controls disabled trigger opacity. |
| --tooltip-focus-ring-color | var(--color-ring) | Controls trigger focus ring color. |
| --tooltip-focus-ring-offset | -1px | Controls trigger focus ring offset. |
| --tooltip-focus-ring-width | var(--border-width-sm) | Controls trigger focus ring width. |
| --tooltip-font-size | var(--text-sm) | Controls the popup font size. |
| --tooltip-line-height | var(--line-height-text-sm) | Controls the popup line height. |
| --tooltip-max-height | 24rem | Controls the popup max height. |
| --tooltip-max-width | 20rem | Controls the popup max width. |
| --tooltip-padding-x | 0.5rem | Controls the popup horizontal padding. |
| --tooltip-padding-y | 0.25rem | Controls the popup vertical padding. |
| --tooltip-radius | var(--radius-md) | Controls the popup border radius. |
| --tooltip-scale | var(--scale-popup) | Controls the popup enter and exit scale. |
| --tooltip-shadow | var(--shadow-lg) | Controls the popup shadow. |
| --tooltip-transition | 150ms | Controls popup and trigger transitions. |
| --tooltip-trigger-bg | var(--color-background) | Controls trigger background color. |
| --tooltip-trigger-bg-active | var(--tooltip-trigger-bg-hover, var(--color-accent)) | Controls trigger background while the tooltip is open. |
| --tooltip-trigger-bg-hover | var(--color-accent) | Controls trigger hover background. |
| --tooltip-trigger-border-color | var(--color-border) | Controls trigger border color. |
| --tooltip-trigger-border-width | var(--border-width-sm) | Controls trigger border width. |
| --tooltip-trigger-color | var(--color-foreground) | Controls trigger text color. |
| --tooltip-trigger-font-size | var(--text-sm) | Controls trigger font size. |
| --tooltip-trigger-ghost-size | var(--size-lg) | Controls icon-only trigger size. |
| --tooltip-trigger-height | var(--size-lg) | Controls trigger height. |
| --tooltip-trigger-line-height | var(--line-height-text-sm) | Controls trigger line height. |
| --tooltip-trigger-padding-x | 0.875rem | Controls trigger horizontal padding. |
| --tooltip-trigger-padding-y | 0.5rem | Controls trigger vertical padding. |
| --tooltip-trigger-radius | var(--radius-md) | Controls trigger border radius. |
| --tooltip-width | max-content | Controls the popup width. |
Interactive variables scoped for docs preview without changing size scale tokens.
| Property | Value | Default | Description |
|---|---|---|---|
| --tooltip-bg | var(--color-popover) | Controls popup background color. | |
| --tooltip-border-color | var(--color-border) | Controls popup border color. | |
| --tooltip-color | var(--color-popover-foreground) | Controls popup text color. | |
| --tooltip-font-size | var(--text-sm) | Controls popup font size. | |
| --tooltip-focus-ring-color | var(--color-ring) | Controls trigger focus ring color. | |
| --tooltip-radius | var(--radius-md) | Controls popup border radius. | |
| --tooltip-shadow | var(--shadow-lg) | Controls popup shadow. | |
| --tooltip-trigger-bg | var(--color-background) | Controls trigger background color. | |
| --tooltip-trigger-bg-hover | var(--color-accent) | Controls trigger hover background. | |
| --tooltip-trigger-color | var(--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>| Part | Role |
|---|---|
Tooltip | Root state and timing behavior. Handles open state, delay, controlled props, and optional handle linking. |
TooltipTrigger | Interactive anchor element. Opens the tooltip on hover/focus and should keep its own accessible name. |
TooltipContent | Composed 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;}