Skip to content

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. Additionally, this makes the tooltip appear right above the mouse cursor, similar to the followCursor option:

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.

Unintrusive

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',
      },
    },
  },
})
Flip on update

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',
      },
    },
  },
})
Hide on scroll

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)
})

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
  }
})

Virtual elements

In some cases you may need to use a virtual element instead of a real DOM element. Pass a plain object with the following properties in:

const virtualElement = {
  getBoundingClientRect() {
    return {
      width: 0,
      height: 0,
      top: 100,
      right: 100,
      bottom: 100,
      left: 100,
    }
  },
  clientWidth: 0,
  clientHeight: 0,
}

tippy(virtualElement)

Change the numbers to suit your needs. Popper.js uses these properties to position the tippy.