0
# Headless Rendering
1
2
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.
3
4
## Capabilities
5
6
### Basic Headless Tooltip
7
8
Creates a tooltip using a custom render function instead of built-in rendering.
9
10
```typescript { .api }
11
/**
12
* Headless Tippy component imported from @tippyjs/react/headless
13
* @param props - TippyProps with render function
14
* @returns React component with custom tooltip rendering
15
*/
16
declare const Tippy: React.ForwardRefExoticComponent<TippyProps>;
17
18
interface TippyProps {
19
/** Custom render function for complete control over tooltip appearance */
20
render?: (
21
attrs: {
22
'data-placement': Placement;
23
'data-reference-hidden'?: string;
24
'data-escaped'?: string;
25
},
26
content?: React.ReactNode,
27
instance?: Instance
28
) => React.ReactNode;
29
/** Child element to attach tooltip to */
30
children?: React.ReactElement<any>;
31
/** Tooltip content passed to render function */
32
content?: React.ReactNode;
33
/** Other Tippy.js props for positioning and behavior */
34
[key: string]: any;
35
}
36
```
37
38
**Usage Examples:**
39
40
```typescript
41
import React from 'react';
42
import Tippy from '@tippyjs/react/headless';
43
44
// Basic headless tooltip
45
function BasicHeadless() {
46
return (
47
<Tippy
48
render={attrs => (
49
<div className="custom-tooltip" tabIndex={-1} {...attrs}>
50
My custom tooltip
51
</div>
52
)}
53
>
54
<button>Hover me</button>
55
</Tippy>
56
);
57
}
58
59
// With content prop
60
function HeadlessWithContent() {
61
return (
62
<Tippy
63
content="Dynamic content"
64
render={(attrs, content) => (
65
<div className="tooltip-box" tabIndex={-1} {...attrs}>
66
{content}
67
</div>
68
)}
69
>
70
<button>Hover me</button>
71
</Tippy>
72
);
73
}
74
```
75
76
### Render Function Attributes
77
78
The render function receives positioning and state attributes that should be applied to your custom element.
79
80
```typescript { .api }
81
interface RenderAttrs {
82
/** Current placement of the tooltip */
83
'data-placement': Placement;
84
/** Present when reference element is hidden */
85
'data-reference-hidden'?: string;
86
/** Present when tooltip has escaped its boundary */
87
'data-escaped'?: string;
88
}
89
```
90
91
**Usage Examples:**
92
93
```typescript
94
// Conditional styling based on attributes
95
function ConditionalStyling() {
96
return (
97
<Tippy
98
render={attrs => {
99
const isHidden = attrs['data-reference-hidden'] !== undefined;
100
const hasEscaped = attrs['data-escaped'] !== undefined;
101
102
return (
103
<div
104
className={`tooltip ${isHidden ? 'hidden' : ''} ${hasEscaped ? 'escaped' : ''}`}
105
tabIndex={-1}
106
{...attrs}
107
>
108
Conditional tooltip
109
</div>
110
);
111
}}
112
>
113
<button>Hover me</button>
114
</Tippy>
115
);
116
}
117
```
118
119
### CSS-in-JS Integration
120
121
Perfect integration with styled-components, emotion, and other CSS-in-JS libraries.
122
123
**Usage Examples:**
124
125
```typescript
126
import React from 'react';
127
import Tippy from '@tippyjs/react/headless';
128
import styled from 'styled-components';
129
130
const TooltipBox = styled.div`
131
background: #333;
132
color: white;
133
padding: 8px 12px;
134
border-radius: 4px;
135
font-size: 14px;
136
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
137
138
&[data-placement^='top'] {
139
margin-bottom: 8px;
140
}
141
142
&[data-placement^='bottom'] {
143
margin-top: 8px;
144
}
145
`;
146
147
function StyledTooltip() {
148
return (
149
<Tippy
150
render={(attrs, content) => (
151
<TooltipBox tabIndex={-1} {...attrs}>
152
{content}
153
</TooltipBox>
154
)}
155
content="Styled with CSS-in-JS"
156
>
157
<button>Styled tooltip</button>
158
</Tippy>
159
);
160
}
161
```
162
163
### Animation Libraries
164
165
Integration with Framer Motion, React Spring, and other animation libraries.
166
167
**Usage Examples:**
168
169
```typescript
170
import React from 'react';
171
import Tippy from '@tippyjs/react/headless';
172
import { motion } from 'framer-motion';
173
174
// Framer Motion animation
175
function AnimatedTooltip() {
176
return (
177
<Tippy
178
render={(attrs, content) => (
179
<motion.div
180
tabIndex={-1}
181
{...attrs}
182
initial={{ opacity: 0, scale: 0.8 }}
183
animate={{ opacity: 1, scale: 1 }}
184
exit={{ opacity: 0, scale: 0.8 }}
185
transition={{ duration: 0.2 }}
186
style={{
187
background: '#333',
188
color: 'white',
189
padding: '8px 12px',
190
borderRadius: '4px',
191
}}
192
>
193
{content}
194
</motion.div>
195
)}
196
content="Animated tooltip"
197
animation={false} // Disable built-in animation
198
>
199
<button>Animated tooltip</button>
200
</Tippy>
201
);
202
}
203
```
204
205
### Custom Arrow
206
207
Create custom arrows with proper positioning using Popper.js attributes.
208
209
```typescript { .api }
210
// Arrow must be an HTMLElement with data-popper-arrow attribute
211
```
212
213
**Usage Examples:**
214
215
```typescript
216
import React from 'react';
217
import Tippy from '@tippyjs/react/headless';
218
219
// CSS arrow with data-popper-arrow
220
function CustomArrowTooltip() {
221
return (
222
<Tippy
223
render={attrs => (
224
<div className="tooltip-with-arrow" tabIndex={-1} {...attrs}>
225
Custom tooltip content
226
<div data-popper-arrow className="arrow" />
227
</div>
228
)}
229
>
230
<button>Arrow tooltip</button>
231
</Tippy>
232
);
233
}
234
235
// Ref-based arrow positioning
236
function RefArrowTooltip() {
237
const [arrow, setArrow] = React.useState<HTMLDivElement | null>(null);
238
239
return (
240
<Tippy
241
render={attrs => (
242
<div className="tooltip" tabIndex={-1} {...attrs}>
243
Content
244
<div ref={setArrow} className="arrow" />
245
</div>
246
)}
247
popperOptions={{
248
modifiers: [
249
{
250
name: 'arrow',
251
options: {
252
element: arrow,
253
},
254
},
255
],
256
}}
257
>
258
<button>Ref arrow</button>
259
</Tippy>
260
);
261
}
262
```
263
264
### Instance Access
265
266
Access the Tippy instance for advanced control and method calls.
267
268
```typescript { .api }
269
interface RenderFunction {
270
(
271
attrs: RenderAttrs,
272
content?: React.ReactNode,
273
instance?: Instance
274
): React.ReactNode;
275
}
276
```
277
278
**Usage Examples:**
279
280
```typescript
281
// Instance methods and properties
282
function InstanceAccess() {
283
return (
284
<Tippy
285
render={(attrs, content, instance) => (
286
<div className="tooltip" tabIndex={-1} {...attrs}>
287
<div>{content}</div>
288
<button onClick={() => instance?.hide()}>
289
Close
290
</button>
291
</div>
292
)}
293
content="Tooltip with close button"
294
interactive={true}
295
>
296
<button>Interactive tooltip</button>
297
</Tippy>
298
);
299
}
300
```
301
302
### Design System Integration
303
304
Perfect for integrating with existing design systems and component libraries.
305
306
**Usage Examples:**
307
308
```typescript
309
import React from 'react';
310
import Tippy from '@tippyjs/react/headless';
311
import { Card, Text } from './design-system';
312
313
function DesignSystemTooltip() {
314
return (
315
<Tippy
316
render={(attrs, content) => (
317
<Card
318
elevation="high"
319
padding="sm"
320
tabIndex={-1}
321
{...attrs}
322
>
323
<Text size="sm">{content}</Text>
324
</Card>
325
)}
326
content="Design system tooltip"
327
>
328
<button>Design system</button>
329
</Tippy>
330
);
331
}
332
```
333
334
### Plugin Support
335
336
Headless rendering also supports Tippy.js plugins for extended functionality.
337
338
**Usage Examples:**
339
340
```typescript
341
import React from 'react';
342
import Tippy from '@tippyjs/react/headless';
343
import { followCursor } from 'tippy.js/headless'; // Note: import from headless
344
345
function HeadlessWithPlugin() {
346
return (
347
<Tippy
348
render={(attrs, content) => (
349
<div className="custom-tooltip" tabIndex={-1} {...attrs}>
350
{content}
351
</div>
352
)}
353
content="I follow the cursor!"
354
followCursor={true}
355
plugins={[followCursor]}
356
>
357
<button>Headless with plugin</button>
358
</Tippy>
359
);
360
}
361
```
362
363
Note: When using headless rendering, import plugins from `tippy.js/headless` instead of `tippy.js`.
364
365
## Import Path
366
367
Always import headless Tippy from the dedicated path:
368
369
```typescript
370
import Tippy from '@tippyjs/react/headless'; // Correct
371
import Tippy from '@tippyjs/react'; // Wrong - this is default rendering
372
```
373
374
## Key Considerations
375
376
- Always include `tabIndex={-1}` on your custom tooltip element for accessibility
377
- Spread the `attrs` object to ensure proper positioning and behavior
378
- The `className` prop doesn't work in headless mode - apply styles directly to your custom element
379
- Disable built-in animations with `animation={false}` when using custom animations
380
- For SVG arrows, wrap them in a `div` with the `data-popper-arrow` attribute