React component wrapper for Tippy.js providing complete tooltip, popover, dropdown, and menu solution for the web
—
Headless rendering provides complete control over tooltip appearance and behavior through a custom render function. This approach is ideal for CSS-in-JS libraries, design systems, and when you need full control over the tooltip's DOM structure and styling.
Creates a tooltip using a custom render function instead of built-in rendering.
/**
* Headless Tippy component imported from @tippyjs/react/headless
* @param props - TippyProps with render function
* @returns React component with custom tooltip rendering
*/
declare const Tippy: React.ForwardRefExoticComponent<TippyProps>;
interface TippyProps {
/** Custom render function for complete control over tooltip appearance */
render?: (
attrs: {
'data-placement': Placement;
'data-reference-hidden'?: string;
'data-escaped'?: string;
},
content?: React.ReactNode,
instance?: Instance
) => React.ReactNode;
/** Child element to attach tooltip to */
children?: React.ReactElement<any>;
/** Tooltip content passed to render function */
content?: React.ReactNode;
/** Other Tippy.js props for positioning and behavior */
[key: string]: any;
}Usage Examples:
import React from 'react';
import Tippy from '@tippyjs/react/headless';
// Basic headless tooltip
function BasicHeadless() {
return (
<Tippy
render={attrs => (
<div className="custom-tooltip" tabIndex={-1} {...attrs}>
My custom tooltip
</div>
)}
>
<button>Hover me</button>
</Tippy>
);
}
// With content prop
function HeadlessWithContent() {
return (
<Tippy
content="Dynamic content"
render={(attrs, content) => (
<div className="tooltip-box" tabIndex={-1} {...attrs}>
{content}
</div>
)}
>
<button>Hover me</button>
</Tippy>
);
}The render function receives positioning and state attributes that should be applied to your custom element.
interface RenderAttrs {
/** Current placement of the tooltip */
'data-placement': Placement;
/** Present when reference element is hidden */
'data-reference-hidden'?: string;
/** Present when tooltip has escaped its boundary */
'data-escaped'?: string;
}Usage Examples:
// Conditional styling based on attributes
function ConditionalStyling() {
return (
<Tippy
render={attrs => {
const isHidden = attrs['data-reference-hidden'] !== undefined;
const hasEscaped = attrs['data-escaped'] !== undefined;
return (
<div
className={`tooltip ${isHidden ? 'hidden' : ''} ${hasEscaped ? 'escaped' : ''}`}
tabIndex={-1}
{...attrs}
>
Conditional tooltip
</div>
);
}}
>
<button>Hover me</button>
</Tippy>
);
}Perfect integration with styled-components, emotion, and other CSS-in-JS libraries.
Usage Examples:
import React from 'react';
import Tippy from '@tippyjs/react/headless';
import styled from 'styled-components';
const TooltipBox = styled.div`
background: #333;
color: white;
padding: 8px 12px;
border-radius: 4px;
font-size: 14px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
&[data-placement^='top'] {
margin-bottom: 8px;
}
&[data-placement^='bottom'] {
margin-top: 8px;
}
`;
function StyledTooltip() {
return (
<Tippy
render={(attrs, content) => (
<TooltipBox tabIndex={-1} {...attrs}>
{content}
</TooltipBox>
)}
content="Styled with CSS-in-JS"
>
<button>Styled tooltip</button>
</Tippy>
);
}Integration with Framer Motion, React Spring, and other animation libraries.
Usage Examples:
import React from 'react';
import Tippy from '@tippyjs/react/headless';
import { motion } from 'framer-motion';
// Framer Motion animation
function AnimatedTooltip() {
return (
<Tippy
render={(attrs, content) => (
<motion.div
tabIndex={-1}
{...attrs}
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.8 }}
transition={{ duration: 0.2 }}
style={{
background: '#333',
color: 'white',
padding: '8px 12px',
borderRadius: '4px',
}}
>
{content}
</motion.div>
)}
content="Animated tooltip"
animation={false} // Disable built-in animation
>
<button>Animated tooltip</button>
</Tippy>
);
}Create custom arrows with proper positioning using Popper.js attributes.
// Arrow must be an HTMLElement with data-popper-arrow attributeUsage Examples:
import React from 'react';
import Tippy from '@tippyjs/react/headless';
// CSS arrow with data-popper-arrow
function CustomArrowTooltip() {
return (
<Tippy
render={attrs => (
<div className="tooltip-with-arrow" tabIndex={-1} {...attrs}>
Custom tooltip content
<div data-popper-arrow className="arrow" />
</div>
)}
>
<button>Arrow tooltip</button>
</Tippy>
);
}
// Ref-based arrow positioning
function RefArrowTooltip() {
const [arrow, setArrow] = React.useState<HTMLDivElement | null>(null);
return (
<Tippy
render={attrs => (
<div className="tooltip" tabIndex={-1} {...attrs}>
Content
<div ref={setArrow} className="arrow" />
</div>
)}
popperOptions={{
modifiers: [
{
name: 'arrow',
options: {
element: arrow,
},
},
],
}}
>
<button>Ref arrow</button>
</Tippy>
);
}Access the Tippy instance for advanced control and method calls.
interface RenderFunction {
(
attrs: RenderAttrs,
content?: React.ReactNode,
instance?: Instance
): React.ReactNode;
}Usage Examples:
// Instance methods and properties
function InstanceAccess() {
return (
<Tippy
render={(attrs, content, instance) => (
<div className="tooltip" tabIndex={-1} {...attrs}>
<div>{content}</div>
<button onClick={() => instance?.hide()}>
Close
</button>
</div>
)}
content="Tooltip with close button"
interactive={true}
>
<button>Interactive tooltip</button>
</Tippy>
);
}Perfect for integrating with existing design systems and component libraries.
Usage Examples:
import React from 'react';
import Tippy from '@tippyjs/react/headless';
import { Card, Text } from './design-system';
function DesignSystemTooltip() {
return (
<Tippy
render={(attrs, content) => (
<Card
elevation="high"
padding="sm"
tabIndex={-1}
{...attrs}
>
<Text size="sm">{content}</Text>
</Card>
)}
content="Design system tooltip"
>
<button>Design system</button>
</Tippy>
);
}Headless rendering also supports Tippy.js plugins for extended functionality.
Usage Examples:
import React from 'react';
import Tippy from '@tippyjs/react/headless';
import { followCursor } from 'tippy.js/headless'; // Note: import from headless
function HeadlessWithPlugin() {
return (
<Tippy
render={(attrs, content) => (
<div className="custom-tooltip" tabIndex={-1} {...attrs}>
{content}
</div>
)}
content="I follow the cursor!"
followCursor={true}
plugins={[followCursor]}
>
<button>Headless with plugin</button>
</Tippy>
);
}Note: When using headless rendering, import plugins from tippy.js/headless instead of tippy.js.
Always import headless Tippy from the dedicated path:
import Tippy from '@tippyjs/react/headless'; // Correct
import Tippy from '@tippyjs/react'; // Wrong - this is default renderingtabIndex={-1} on your custom tooltip element for accessibilityattrs object to ensure proper positioning and behaviorclassName prop doesn't work in headless mode - apply styles directly to your custom elementanimation={false} when using custom animationsdiv with the data-popper-arrow attributeInstall with Tessl CLI
npx tessl i tessl/npm-tippyjs--react