Misc
Groups
Groups are designed to improve user experience when tooltips have delays. When moving the cursor between each item in the group, the tooltips will instantly show.
Compare the following tooltips without grouping:
tippy('button', { delay: 1000 })
Now compare the following tooltips with grouping:
tippy.group(tippy('button', { delay: 1000 }))
As you can see, the user experience is much nicer. To enable group behavior,
call the method tippy.group()
and pass in an array of Tippy instances.
The group method also takes an optional options object as a second argument:
tippy.group(instances, {
delay: 1000, // if the instances don't specify a `delay`
duration: 50, // instead of 0 (instant duration)
})
Anchor links
When an inline anchor link spans two or more lines, the tippy gets centered as though it were attached to a block:
If this is a problem (e.g. with interactivity), there is a way to solve it:
It requires using a virtual positioning reference. See the CodePen demo.
Scrollable containers
Here are 3 common solutions. Note that no solution is completely ideal in all
situations - it depends on the UI. In each of these, if you want the tippy to be
clipped by the scrolling area, use appendTo: "parent"
. If the tippy is large
then this may be undesirable.
With this technique, the tooltip acts like it does on the document normally. It gets clipped by the scrolling area and does not attempt to position itself to be fully visible (but will still flip).
tippy('#unintrusive-example', {
boundary: 'window',
appendTo: 'parent',
popperOptions: {
modifiers: {
flip: {
boundariesElement: 'scrollParent',
},
},
},
})
With this technique, the tooltip attempts to reposition itself to be visible to the user at all times. Sometimes this can be a little overly intrusive, though.
tippy('#scroll-flipping-example', {
appendTo: 'parent',
flipOnUpdate: true,
// Leave this out if you want flipping to occur based on the
// viewport rather than the container
popperOptions: {
modifiers: {
flip: {
boundariesElement: 'scrollParent',
},
},
},
})
With this technique, the tooltip hides instantly whenever the user starts scrolling. In addition, it initially positions itself to be as visible as possible within the scrolling area.
const instance = tippy(document.querySelector('#hide-on-scroll-example'))
const container = document.querySelector('#scrollable-container')
container.addEventListener('scroll', () => {
instance.hide(0)
})
Different trigger target
You may want the tippy to appear at a different location from its trigger (event listeners) target. For example:
For this, you can utilize the triggerTarget
option:
const innerOrangeSpanElement = document.querySelector('span')
const outerDivElement = document.querySelector('div')
tippy(innerOrangeSpanElement, {
triggerTarget: outerDivElement,
})
Custom animations
Custom animations can be created, but require a bit of CSS to setup. The default
"shift-away"
animation has a hardcoded translation of 10px. Sometimes, for
small elements, this can be a bit much, especially if the tippy has an arrow.
You need to:
- Target each different basic placement in
x-placement
:top
,bottom
,left
,right
- Target the two visibility states in
data-state
:visible
andhidden
- Use your own animation name in
data-animation
Using a preprocessor with nesting support (e.g. SASS) helps reduce the boilerplate and amount of code required.
/* Subtle shift-away animation */
/* Top */
.tippy-tooltip[x-placement^='top'][data-animation='subtle-shift-away'][data-state='visible'] {
transform: translateY(-10px);
opacity: 1;
}
.tippy-tooltip[x-placement^='top'][data-animation='subtle-shift-away'][data-state='hidden'] {
transform: translateY(-5px);
opacity: 0;
}
/* Bottom */
.tippy-tooltip[x-placement^='bottom'][data-animation='subtle-shift-away'][data-state='visible'] {
transform: translateY(10px);
opacity: 1;
}
.tippy-tooltip[x-placement^='bottom'][data-animation='subtle-shift-away'][data-state='hidden'] {
transform: translateY(5px);
opacity: 0;
}
/* Left */
.tippy-tooltip[x-placement^='left'][data-animation='subtle-shift-away'][data-state='visible'] {
transform: translateX(-10px);
opacity: 1;
}
.tippy-tooltip[x-placement^='left'][data-animation='subtle-shift-away'][data-state='hidden'] {
transform: translateX(-5px);
opacity: 0;
}
/* Right */
.tippy-tooltip[x-placement^='right'][data-animation='subtle-shift-away'][data-state='visible'] {
transform: translateX(10px);
opacity: 1;
}
.tippy-tooltip[x-placement^='right'][data-animation='subtle-shift-away'][data-state='hidden'] {
transform: translateX(5px);
opacity: 0;
}
Why do the attribute names have a different prefix (
x-*
anddata-*
)?
Popper.js (v1) uses the x-
prefix convention, while Tippy.js adheres to the
HTML5 spec with data-
prefix. Popper.js v2 will change the prefix to match
Tippy's.
Animate.css animations
Want more animations than the built-in ones? You can use any CSS animation you like, for example, animate.css. Include it on your page and then add/remove the classes onMount/onHidden:
tippy('button', {
animation: 'fade',
// If you don't want the filling effect as well
animateFill: false,
// The default CSS uses `transform: translateY(10px)`, but animated.css transforms
// will override it, so we'll need to use +10 more distance than the default (10)
distance: 20,
onMount(instance) {
const { tooltip } = instance.popperChildren
requestAnimationFrame(() => {
tooltip.classList.add('animated')
tooltip.classList.add('wobble')
})
},
onHidden(instance) {
const { tooltip } = instance.popperChildren
tooltip.classList.remove('animated')
tooltip.classList.remove('wobble')
},
})
Event delegation
Event delegation allows you to bind a Tippy instance to a parent container element to handle creation of tippys for children.
This allows two things:
- It eliminates the need to create new instances for new children getting appended to the parent.
- It improves performance as only a single instance is created, and event bubbling handles creation of instances at the right time.
Children will inherit the options specified in the parent instance, but
individual options can be specified via data-tippy-*
attributes.
<div id="parent">
<button
class="child"
data-tippy-content="Tooltip 1"
>
One
</button>
<button
class="child"
data-tippy-content="Tooltip 2"
data-tippy-arrow="true"
>
Two
</button>
<button
class="child"
data-tippy-content="Tooltip 3"
data-tippy-theme="light"
>
Three
</button>
</div>
To enable this behavior, bind the instance to the #parent
element and specify
a target
option with a CSS selector that matches the child elements which
should receive tooltips:
tippy('#parent', {
target: '.child',
})
Hide tooltips on scroll
window.addEventListener('scroll', () => {
// If you want to use their own durations
tippy.hideAll()
// Or, if you want to make them all hide instantly
tippy.hideAll({ duration: 0 })
})
Buttons with tooltips on touch devices
A tooltip on a button is generally used to convey information before the user decides to click on it. On touch devices, this isn't possible because a tap is required to show the tooltip, which will fire a click event.
On iOS, a tap will show the tooltip but click events won't fire until a second tap. This allows the user to see the tooltip before deciding to click the button. On Android, clicking the button will show the tooltip and also fire a click event.
Depending on your use case, one of these will be preferred, so user agent checking may be needed.
If neither behavior is preferred, consider using the touchHold
option which
allows the user to see the tooltip while pressing and holding the button, but
won't fire a click event unless the click appears to be intentional.
const button = document.querySelector('button')
const isIOS = /iPhone|iPad|iPod/.test(navigator.platform)
/*========================================================
A: Use `touchHold: true`
========================================================*/
tippy(button, {
touchHold: true,
})
/*========================================================
B: Make iOS behave like Android (single tap to click)
========================================================*/
button.addEventListener('click', () => {
// Your logic
})
tippy(button, {
onShow() {
if (isIOS) {
button.click()
}
},
})
/*========================================================
C: Make Android behave like iOS (double tap to click)
========================================================*/
// Useful function for dynamically determining the input type:
// https://github.com/30-seconds/30-seconds-of-code#onuserinputchange
let isUsingTouch = false
onUserInputChange(type => {
isUsingTouch = type === 'touch'
})
const instance = tippy(button)
button.addEventListener('click', () => {
if (isIOS || !isUsingTouch ? true : instance.state.isShown) {
// Your logic
}
})
onMouseRest
implementation
You might desire to use this functionality instead of using a delay
. Instead,
the tippy will only show once the cursor is resting over the element, but not
while it continues to move around over it.
const button = document.querySelector('button')
const instance = tippy(button, {
trigger: 'focus',
})
let timeout
button.addEventListener('mousemove', () => {
clearTimeout(timeout)
timeout = setTimeout(() => instance.show(), 125)
})
button.addEventListener('mouseleave', () => {
clearTimeout(timeout)
instance.hide()
})
Virtual elements
In some cases you may need to use a virtual element instead of a real DOM
element. Pass a plain object with a getBoundingClientRect
method in:
tippy({
getBoundingClientRect: () => ({
width: 0,
height: 0,
top: 100,
right: 100,
bottom: 100,
left: 100,
}),
})
Change the numbers to suit your needs. Popper.js uses getBoundingClientRect()
to position the tippy.