Radix homepageRadix Homepage
PrimitivesAlpha

Tooltip

A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it.

import React from 'react';
import { styled, keyframes } from '@stitches/react';
import { PlusIcon } from '@radix-ui/react-icons';
import { violet, blackA } from '@radix-ui/colors';
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
const slideUpAndFade = keyframes({
'0%': { opacity: 0, transform: 'translateY(2px)' },
'100%': { opacity: 1, transform: 'translateY(0)' },
});
const slideRightAndFade = keyframes({
'0%': { opacity: 0, transform: 'translateX(-2px)' },
'100%': { opacity: 1, transform: 'translateX(0)' },
});
const slideDownAndFade = keyframes({
'0%': { opacity: 0, transform: 'translateY(-2px)' },
'100%': { opacity: 1, transform: 'translateY(0)' },
});
const slideLeftAndFade = keyframes({
'0%': { opacity: 0, transform: 'translateX(2px)' },
'100%': { opacity: 1, transform: 'translateX(0)' },
});
const StyledContent = styled(TooltipPrimitive.Content, {
borderRadius: 4,
padding: '10px 15px',
fontSize: 15,
lineHeight: 1,
color: violet.violet11,
backgroundColor: 'white',
boxShadow: 'hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px',
'@media (prefers-reduced-motion: no-preference)': {
animationDuration: '400ms',
animationTimingFunction: 'cubic-bezier(0.16, 1, 0.3, 1)',
willChange: 'transform, opacity',
'&[data-state="delayed-open"]': {
'&[data-side="top"]': { animationName: slideDownAndFade },
'&[data-side="right"]': { animationName: slideLeftAndFade },
'&[data-side="bottom"]': { animationName: slideUpAndFade },
'&[data-side="left"]': { animationName: slideRightAndFade },
},
},
});
const StyledArrow = styled(TooltipPrimitive.Arrow, {
fill: 'white',
});
// Exports
export const Tooltip = TooltipPrimitive.Root;
export const TooltipTrigger = TooltipPrimitive.Trigger;
export const TooltipContent = StyledContent;
// Your app...
const IconButton = styled('button', {
all: 'unset',
fontFamily: 'inherit',
borderRadius: '100%',
height: 35,
width: 35,
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
color: violet.violet11,
backgroundColor: 'white',
boxShadow: `0 2px 10px ${blackA.blackA7}`,
'&:hover': { backgroundColor: violet.violet3 },
'&:focus': { boxShadow: `0 0 0 2px black` },
});
const TooltipDemo = () => {
return (
<Tooltip>
<TooltipTrigger as={IconButton}>
<PlusIcon />
</TooltipTrigger>
<StyledContent sideOffset={5} >
Add to library
<StyledArrow />
</StyledContent>
</Tooltip>
);
};
export default TooltipDemo;

Features

  • Built-in state machine to control display delay.
  • Opens when the trigger is focused or hovered.
  • Closes when the trigger is activated or when pressing escape.
  • Supports custom timings.

Install the component from your command line.

npm install @radix-ui/react-tooltip

Import all parts and piece them together.

import * as Tooltip from '@radix-ui/react-tooltip';
export default () => (
<Tooltip.Root>
<Tooltip.Trigger />
<Tooltip.Content>
<Tooltip.Arrow />
</Tooltip.Content>
</Tooltip.Root>
);

Contains all the parts of a tooltip.

PropTypeDefault
defaultOpenbooleanNo default value
openbooleanNo default value
onOpenChangefunctionNo default value
delayDurationnumber700
skipDelayDurationnumber300

The button that toggles the tooltip. By default, the Tooltip.Content will position itself against the trigger.

PropTypeDefault
asenumbutton

The component that pops out when the tooltip is open.

PropTypeDefault
asenumdiv
aria-labelstringNo default value
portalledbooleantrue
sideenum"bottom"
sideOffsetnumber0
alignenum"center"
alignOffsetnumber0
avoidCollisionsbooleantrue
collisionToleranceboolean0

An optional arrow element to render alongside the tooltip. This can be used to help visually link the trigger with the Tooltip.Content. Must be rendered inside Tooltip.Content.

PropTypeDefault
asenumsvg
widthnumber10
heightnumber5
offsetnumberNo default value

Use the delayDuration prop to control the time it takes for the tooltip to open.

import * as Tooltip from '@radix-ui/react-tooltip';
export default () => (
<Tooltip.Root delayDuration={0}>
<Tooltip.Trigger></Tooltip.Trigger>
<Tooltip.Content></Tooltip.Content>
</Tooltip.Root>
);

Since disabled buttons don't fire events, you need to:

  • Render the Trigger as span.

  • Ensure the button has no pointerEvents.

import * as Tooltip from '@radix-ui/react-tooltip';
export default () => (
<Tooltip.Root>
<Tooltip.Trigger as="span">
<button disabled style={{ pointerEvents: 'none' }}>
</button>
</Tooltip.Trigger>
<Tooltip.Content></Tooltip.Content>
</Tooltip.Root>
);

We expose a CSS custom property --radix-tooltip-content-transform-origin. Use it to animate the content from its computed origin based on side, sideOffset, align, alignOffset and any collisions.

import { styled, keyframes } from '@stitches/react';
import * as Tooltip from '@radix-ui/react-tooltip';
const scaleIn = keyframes({
'0%': { opacity: 0, transform: 'scale(0)' },
'100%': { opacity: 1, transform: 'scale(1)' },
});
const TooltipContent = styled(Tooltip.Content, {
transformOrigin: 'var(--radix-tooltip-content-transform-origin)',
animation: `${scaleIn} 0.5s ease-out`,
});
export default () => (
<Tooltip.Root>
<Tooltip.Trigger></Tooltip.Trigger>
<TooltipContent></TooltipContent>
</Tooltip.Root>
);

We expose data-side and data-align attributes. Their values will change at runtime to reflect collisions. Use them to create collision and direction-aware animations.

import { styled, keyframes } from '@stitches/react';
import * as Tooltip from '@radix-ui/react-tooltip';
const slideDown = keyframes({
'0%': { opacity: 0, transform: 'translateY(-10px)' },
'100%': { opacity: 1, transform: 'translateY(0)' },
});
const slideUp = keyframes({
'0%': { opacity: 0, transform: 'translateY(10px)' },
'100%': { opacity: 1, transform: 'translateY(0)' },
});
const TooltipContent = styled(Tooltip.Content, {
'&[data-side="top"]': { animationName: slideUp },
'&[data-side="bottom"]': { animationName: slideDown },
animationDuration: '0.6s',
animationTimingFunction: 'cubic-bezier(0.16, 1, 0.3, 1)',
});
export default () => (
<Tooltip.Root>
<Tooltip.Trigger></Tooltip.Trigger>
<TooltipContent></TooltipContent>
</Tooltip.Root>
);
KeyDescription
TabOpens/closes the tooltip without delay.
SpaceIf open, closes the tooltip without delay.
EnterIf open, closes the tooltip without delay.
EscapeIf open, closes the tooltip without delay.

Create your own API by abstracting the primitive parts into your own component.

This example abstracts all of the Tooltip parts and introduces a new content prop.

Usage

import { Tooltip } from './your-tooltip';
export default () => (
<Tooltip content="Tooltip content">
<button>Tooltip trigger</button>
</Tooltip>
);

Implementation

Use the Slot utility to convert the trigger part into a slottable area. It will replace the trigger with the child that gets passed to it.

// your-tooltip.jsx
import React from 'react';
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
import { Slot } from '@radix-ui/react-slot';
export function Tooltip({ children, content, open, defaultOpen, onOpenChange, ...props }) {
return (
<TooltipPrimitive.Root open={open} defaultOpen={defaultOpen} onOpenChange={onOpenChange} >
<TooltipPrimitive.Trigger as={Slot}>{children}</TooltipPrimitive.Trigger>
<TooltipPrimitive.Content side="top" align="center" {...props}>
{content}
<TooltipPrimitive.Arrow offset={5} width={11} height={5} />
</TooltipPrimitive.Content>
</TooltipPrimitive.Root>
);
}