Sometimes simple things are hard. Tooltips, for example, seem like a simple thing that should also be easy. Sadly, this is not the case and there have been a number of attempts to make it easier. Tippy is one of the best. Today we’re going to see how to integrate Tippy, the tooltip library, into Astro, the static site generator.
This guide was written for Astro v4.8.6 and Tippy v6.3.7.
Once we’re done, you’ll be able to write tooltips like this:
I’m a tooltip link!There’s already an Astro plugin for Tippy
Whilst looking for an Astro tooltip integration, you may have come across astro-tooltips. While this seems to fit the bill at first, there are a few issues. For one, the plugin is outdated. It breaks with the new Astro View Transition API. The plugin is also quite opinionated, adding yet another API on top of Tippy. This additional layer may, or may not, work with your use case. The integration is simple enough that we might as well just copy a bit of code and retain full control for ourselves.
Requirements
Let’s start with our requirements so we know what we even want:
- We want to set up tooltips site-wide
- We want View Transitions to work
- We want any element with a
[tip]
attribute to be a tooltip - The
[tip]
attribute will contain the tooltip text
For example, we want to write:
<span tip="Aw, thanks">Hover me!</span>
And we want to see: Hover me!
All without doing any extra work per tooltip other than writing the HTML.
The most basic integration
Let’s start with the most basic thing that will get you running. You can totally stop here too if this is all you need. We’ll go into a bit more advanced example later though.
First we’ll need to install Tippy into your Astro project:
npm add tippy.js
Change out npm
for whatever package manager you’d like.
Next, we need to make the Astro component. Save this code into something like components/Tooltip.astro
:
---
import "tippy.js/dist/tippy.css";
---
<script>
import tippy from "tippy.js";
document.addEventListener("astro:page-load", () => {
const tips = document.querySelectorAll("[tip]");
tips.forEach((t) => {
tippy(t, {
content: t.attributes.getNamedItem("tip")?.textContent || "",
});
});
});
</script>
Aaaaaand, that’s basically it. Just add this component into your <head>
somewhere and you’re done. However, let’s break down what’s going on so we can modify it later as needed.
The component starts by importing Tippy’s CSS file. Pretty standard Tippy setup. Next we make a script that runs on page load. This imports the Tippy library, also pretty standard.
Now it gets interesting. In order to make sure the script runs every time there’s a view transition, we add an event listener for astro:page-load
. This technique is from the Astro documentation.
Now we have to find all the elements that contain a [tip]
attribute. We do that with a call to querySelectorAll
. Then we iterate over all those elements, initializing Tippy in the usual way. To obtain the tooltip content, we pull that from the element attributes
property, setting an empty string as a default.
Done! With this set up you can go through the Tippy documentation, adding themes or any other options you’d like. However, I’ll show you how to add a few more features in the next section.
Styles
As it stands, your tooltips won’t look different from regular text. Not good. We’re going to want them to pop a bit so people actually know to hover over these bad boys. Luckily, it’s easy enough. To get your tips to look like the ones on this page, crack open your global CSS file and drop these lines:
span[tip] {
cursor: help;
text-decoration-style: dotted;
text-decoration-line: underline;
text-decoration-color: blue;
text-decoration-thickness: 1.5px;
font-weight: 500;
}
This will style any span
element with a tip
attribute to have a thick dotted line under it. Modify to your liking. You can also select off the .tippy-box
class to modify how the tooltip itself looks. This is discussed in the Tippy docs.
Accessibility
Finally, we need to talk accessibility. Sadly, it’s still one of the last things we think about, but it’s always good to add. Tippy is already pretty accessible, though our integration needs some work still. Specifically, our tooltip triggers need to be selectable via keyboard. This is easy, but it depends on how we are using HTML elements to show our tooltips. For this integration we are using span
and div
elements as anchors. We’ll need to add a bit of ‘script to add tabindex="0"
whenever we find a [tip]
attribute. This is the set-and-forget accessibility we love.
Back to the components/Tooltip.astro
file:
---
import "tippy.js/dist/tippy.css";
---
<script>
import tippy from "tippy.js";
document.addEventListener("astro:page-load", () => {
const tips = document.querySelectorAll("[tip]");
tips.forEach((t) => {
> if (t.tagName == "SPAN" || t.tagName == "DIV") {
> t.setAttribute("tabindex", "0");
> }
tippy(t, {
content: t.attributes.getNamedItem("tip")?.textContent || "",
});
});
});
</script>
We’ve added a small if
statement after forEach
that checks what kind of element the [tip]
attribute is on. If it’s a span
or a div
, we add tabindex="0"
to that element. Now we can page through our tooltips with the keyboard! We did good.
Outro
Anyway, here’s where I ended up. It’s working out ok and I feel confident that I can modify this integration in the future if I need to. I like that the API in my content is a simple span
and a tip
. I can modify the Tooltip component later with a completely different implementation and not have to change any content. Hopefully this was helpful to you as well.