Ryan Chandler

Creating Dynamic HTML Tooltips with Alpine and alpine-tooltip

3 min read

This post was published 2 years ago. Some of the information might be outdated!

After integrating Tippy into a few projects with Alpine.js, I decided to write my own plugin to make the integration easier and the API a little nicer (x-tooltip and $tooltip).

The package itself receives hundreds of thousands of hits via the CDN each month and has become quite popular.

Something that is quite common is using a template element in your HTML to provide Tippy with content. A recent release of my package (v1.2.0) now lets developers provide a raw Tippy configuration object to the x-tooltip directive.

This new integration path makes it simple to use other Alpine directives and <template> tags to generate the content for your tooltip.

Using <template> tags for content

<div x-data="{ message: 'Hello, world!' }">
    <template x-ref="template">
        <p x-text="message"></p>
    </template>

    <button type="button">
        Show message!
    </button>
</div>

I want to use the <template> tag here as my tooltip content. Tippy doesn't know about this element, so we need to use a configuration object to inform Tippy of its existence and innerHTML.

This can be done with the x-tooltip directive on <button> the like so:

<button x-tooltip="{
    content: () => $refs.template.innerHTML,
}">
    Show message!
</button>

The content property on the object is a callback function. This function is invoked by Tippy before each tooltip render and the return value should be a string or an HTML element. We'll use a string since the element itself is a <template> and doesn't actually get rendered by the browser in this scenario.

Tippy will render all HTML as plain text for security reasons. To prevent this, we also need to tell Tippy to allow HTML content using the allowHTML property.

<button x-tooltip="{
    content: () => $refs.template.innerHTML,
    allowHTML: true,
}">
    Show message!
</button>

Hovering over the button now will present an empty element... not so good. If you inspect the DOM, you'll see that Tippy actually appends an HTML element to the <body> of the document via document.body. This element is what appears when hovering or interacting with the button.

Our Alpine component is only scoped to the original <div> where x-data or x-init is defined. It doesn't operate outside of that element and therefore can't process the tooltip's HTML content.

To solve this, Tippy needs to append the dynamic tooltip element to a different element inside of the Alpine component's scope. This can be achieved using the appendTo property.

<button x-tooltip="{
    content: () => $refs.template.innerHTML,
    allowHTML: true,
    appendTo: $root,
}">
    Show message!
</button>

Using the magic $root variable, Tippy will append the tooltip's HTML element to the end of our Alpine component instead of the <body>. This means Alpine can process the HTML and bind text, event listeners etc.

Interactive HTML

The HTML rendered by Tippy won't be interactive by default. This means that links and other elements in the tooltip will essentially be unreachable by the user.

Tippy provides an interactive property that reverses this and allows interactive HTML elements to be interacted with.

<button x-tooltip="{
    content: () => $refs.template.innerHTML,
    allowHTML: true,
    appendTo: $root,
    interactive: true
}">
    Show message!
</button>