Using AJAX in tooltips is facilitated by lifecycle functions:

  • onShow()
  • onMount()
  • onShown()
  • onHide()
  • onHidden()

This allows you to do very powerful things. For example, let's say you wanted to show a new image inside a tooltip each time it gets shown.

const INITIAL_CONTENT = 'Loading...'

tippy('#ajax-tippy', {
  async onShow(tip) {
    // We can monkey-patch the instance's state object with our own state
    if (!tip.state.ajax) {
      tip.state.ajax = {
        isFetching: false,
        canFetch: true,

    if (tip.state.ajax.isFetching || !tip.state.ajax.canFetch) {

    tip.state.ajax.isFetching = true
    tip.state.ajax.canFetch = false

    try {
      const response = await fetch('https://unsplash.it/200/?random')
      const blob = await response.blob()
      const url = URL.createObjectURL(blob)
      if (tip.state.isVisible) {
        const img = new Image()
        img.width = 200
        img.height = 200
        img.src = url
        img.style.display = 'block'
    } catch (e) {
      tip.setContent(`Fetch failed. ${e}`)
    } finally {
      tip.state.ajax.isFetching = false
  onHidden(tip) {
    tip.state.ajax.canFetch = true

However, while this works, it doesn't look very pleasant. The image instantly replaces the Loading... text without any smooth transition.

How do we make it so the tooltip smoothly transitions in height from the Loading... text to the image height?

To look at example code solving this dynamically (i.e. not knowing the height of the image or initial size of the tooltip) view the CodePen demo.