How to Make Magic, Animated Tooltips With CSS

Tooltips are a great way to enhance a UI when your users need some extra context for that fancy icon, or when they want some reassurance for clicking a button, or maybe an Easter Egg caption to go along with an image.

How to Make Magic, Animated Tooltips With CSS

Let’s make some animated tooltips, right now, with nothing but HTML and CSS.

Demo

Here’s what we’re working towards:

Before we get immersed in the cauldron, let’s take a peek at what we’re actually brewing. The main goal is to have a simple way to add a tooltip, so we’ll do that by adding a custom tooltip attribute:

<span tooltip="message">visible text or icon, etc.</span>

A Note About Accessibility and Capability

If you are looking for 508-compliant tooltips, or need smarter tooltips with container collision detection and/or support for HTML content vs. plain text, there are plenty of solutions that use third-party scripts to solve those needs for you.

“JavaScript is imperative to make fully-accessible interactive components.” – Sara Soueidan, Building a fully-accessible help tooltip…is harder than I thought

This tutorial doesn’t specifically address accessibility needs. You know your users and what they need, so be sure to consider their needs in this respect too.

Let’s Set Some Expectations

  • No JavaScript required
  • We’ll be using attribute selectors (not classnames), with CSS built-in pattern matching
  • Add to existing DOM elements (no new elements required in your markup*)
  • Code examples are prefix-free (add vendor prefixes for your target browsers if needed)
  • Assumes mouseover/hover to trigger tooltips
  • Plain text tooltips only (HTML, images, etc. are not supported)
  • Subtle animations while invoking tooltips

Alright! Let’s Rock This Boat!

Oh, wait. We have an asterisk to deal with first, about “not needing any extra markup”. This is magic, after all. Our tooltips don’t really need any extra DOM elements as they are made up completely of pseudo-elements (the ::before and ::after things) which we can control via CSS.

If you’re already utilizing an element’s pseudo-elements from another set of styles and you want a tooltip on that element, then you may need to restructure a little bit.

Ain’t No Party Like A Tooltip Party!

Wait. Gremlins! One more caveat: CSS positioning. In order for the tooltips to function properly, their parent element (the thing to which we are attaching the tooltip) needs to be

  • position: relative, or
  • position: absolute, or
  • position: fixed

Basically, anything other than position: static — that’s the default position mode assigned to pretty much all elements by the browser. The tooltips are positioned absolutely and so they need to know the boundaries in which their absoluteness has meaning. The default position directive static does not declare its own boundaries and will not give our tooltips a context to push against, so the tooltips will use the next closest parental element that does have a declared boundary.

You’ll need to decide which position directive works best with how you are using the tooltips. This tutorial assumes position: relative for the parent element. If your UI relies on an absolutely positioned element, then some restructuring (extra markup) may also be needed to deploy a tooltip on that element.

Let’s jump in and see what’s up.

Attribute Selectors; A Quick Refresher

Most CSS rules are written with classnames in mind, like .this-thing, but CSS has a handful of selector types. Our magic tooltips are going to use attribute selectors–that’s the square bracket notation:

[foo] {
    background: rgba(0, 0, 0, 0.8);
    color: #fff;
}

When the browser encounters something like this:

<span foo>Check it out!</span>

it will know it needs to apply the [foo] rules because that <span> tag has an attribute named foo. In this case, the span itself would have a translucent-black background with white text.

HTML elements have various built-in attributes, but we can also make up our own. Like foo, or tooltip. By default, HTML doesn’t know what these mean, but with CSS we can tell HTML what this means.

Why Attribute Selectors?

We’ll use attribute selectors primarily for a separation of concerns. Using attributes over classnames does not get us any bonus points in the specificity wars; classes and attributes have the same specificity. However, by using attributes we can keep our content with the content as HTML attributes can have values, whereas classnames do not.

Consider the classname .tooltip vs. the attribute [tooltip] in this example code. The classname is one of the values for the attribute [class] while the tooltip attribute has a value, which is the text we want to display.

<span class="tooltip another-classname">lorem ipsum</span>

<span tooltip="sit dolar amet">lorem ipsum</span>

Now Entering Tooltip Alchemy

Our tooltips will use two different attributes:

  • tooltip: this holds the tooltip’s content (a plain text string)
  • flowoptional; this allows us to control how to expose the tooltip. There are many placements we could support but we’ll cover four common placements:up, left, right, down.

Now, let’s set up the ground work for all tooltips. The rules from steps 1–5 apply to all tooltips regardless of what flow we give them. Steps 6–7 have distinctions between the various flow values.

1. Relativity

This is for the tooltip’s parent element. Let’s assign a position directive so the absolute positioning of the tooltip’s parts (the ::before and ::after pseudo-elements) are positioned in context of this parent element and not in context of the page at-large or a grandparent element or some other outer element up the DOM tree.

[tooltip] {
  position: relative;
}

2. Pseudo-element Prime Time

It’s time to prime the pseudo-elements. Here we’ll set common properties to both the ::before and ::after pieces. The content property is what actually makes a pseudo-element work, but we’ll get there soon.

[tooltip]::before,
[tooltip]::after {
    line-height: 1;
    user-select: none;
    pointer-events: none;
    position: absolute;
    display: none;
    opacity: 0;

    /* opinions */
    text-transform: none; 
    font-size: .9em;
}

3. The Dink

I don’t know why “dink” makes sense, I’ve just always called it that. This is the little triangle pointy part that gives the tooltips their speech bubble feel by pointing at the thing which invoked it. Notice we’re using transparent for the border color; we’,ll add in the color later as how we add it depends on the tooltip’s flow.

[tooltip]::before {
    content: '';
    z-index: 1001;
    border: 5px solid transparent;
}

It’s not a typo that the content: ''; declaration has an empty string for a value. We don’t want anything in there, but we do need that property for the pseudo-element to exist.

To make a triangle we are defining a solid border with some thickness on an empty box (no content) with no width and no height, and only giving one side of the box a border color. For more details check out the following tutorial:

  • How to Make Magic, Animated Tooltips With CSS
    CSS
    Quick Tip: Drawing and Using CSS Triangles
    Joren Van Hee

4. Bubbles!

Here is the meat of the thing. Notice the content: attr(tooltip) part saying, “This pseudo-element should use the value of the tooltip attribute as its content.” This is why using attributes over classnames is so great!

[tooltip]::after {
    content: attr(tooltip); /* magic! */
    z-index: 1000;
    
    /* most of the rest of this is opinion */
    font-family: Helvetica, sans-serif;
    text-align: center;
    
    /* 
    Let the content set the size of the tooltips 
    but this will also keep them from being obnoxious
    */
    min-width: 3em;
    max-width: 21em;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    
    /* visible design of the tooltip bubbles */
    padding: 1ch 1.5ch;
    border-radius: .3ch;
    box-shadow: 0 1em 2em -.5em rgba(0, 0, 0, 0.35);
    background: #333;
    color: #fff;
}

Notice the z-index values for both the dink and the bubble. These are arbitrary values, but keep in mind that a z-index value is relative. Meaning: a z-index value of 1001 inside an element with a z-index of 3 just means that the 1001 element will be the top-most element inside that z-index: 3 container.

The bubble’s z-index should be at least one step down from the dink’s z-index. If it is the same as or higher than the dink, you can end up with an inconsistent coloring effect on the dink if your tooltips employ a box-shadow.

For a more detailed look at the z-index property, take a look at the following tutorial:

  • How to Make Magic, Animated Tooltips With CSS
    CSS
    What You May Not Know About the Z-Index Property
    Steven Bradley

5. Interaction Action

Our tooltips are activated by hovering the mouse over the element with the tooltip… Almost.

[tooltip]:hover::before,
[tooltip]:hover::after {
    display: block;
}

If you look back at our style block in Step 2, you should see that we’ve used opacity: 0; along with display: none; for our tooltip parts. We did this so we can use CSS animation effects when the tooltips show and hide.

The display property cannot be animated, but opacity can! We’ll deal with the animations last. If you don’t care for animated tooltips, just wipe out the opacity: 0; declaration from Step 2 and ignore the animation in Step 7.

The last thing we’ll need that still applies to all the tooltips is a way to suppress a tooltip if it has no content. If you are populating tooltips with some sort of dynamic system (Vue.js, Angular, or React, PHP, etc.) we don’t want silly empty bubbles!

/* don't show empty tooltips */
[tooltip='']::before,
[tooltip='']::after {
    display: none !important;
}

6. Flow Control

This step can get rather complicated as we’ll be using some not so common selectors to help our tooltips deal with their placements based on their flow values (or lack thereof).

“Strange things are afoot at the Circle-K.” — Ted Theodore Logan

Before we jump into the styles, let’s take a look at some selector patterns we’ll be using.

[tooltip]:not([flow])::before,
[tooltip][flow^="up"]::before {
    /* ...
    properties: values
    ... */
}

This is telling the browser: “For all elements with a tooltip attribute that either do not have a flow attribute, or have a flow with a value that starts with ‘up’: apply these styles to its ::before pseudo-element.”

We’re using a pattern here so these can be extended to other flows without needing to repeat so much CSS. This pattern flow^="up" is using the ^= (starts with) matcher. This allows the styles to also apply to up-right and up-left should you want to add those flow controls. We’re not going to cover those here, but you can see them in use on my original tooltip demo on CodePen.

Here are the CSS blocks for all four flows this tutorial covers.

Up (default):

/* ONLY the ::before */
[tooltip]:not([flow])::before,
[tooltip][flow^="up"]::before {
    bottom: 100%;
    border-bottom-width: 0;
    border-top-color: #333;
}

/* ONLY the ::after */
[tooltip]:not([flow])::after,
[tooltip][flow^="up"]::after {
    bottom: calc(100% + 5px);
}

/* Both ::before & ::after */
[tooltip]:not([flow])::before,
[tooltip]:not([flow])::after,
[tooltip][flow^="up"]::before,
[tooltip][flow^="up"]::after {
    left: 50%;
    transform: translate(-50%, -.5em);
}

Down:

[tooltip][flow^="down"]::before {
    top: 100%;
    border-top-width: 0;
    border-bottom-color: #333;
}

[tooltip][flow^="down"]::after {
    top: calc(100% + 5px);
}

[tooltip][flow^="down"]::before,
[tooltip][flow^="down"]::after {
    left: 50%;
    transform: translate(-50%, .5em);
}

Left:

[tooltip][flow^="left"]::before {
    top: 50%;
    border-right-width: 0;
    border-left-color: #333;
    left: calc(0em - 5px);
    transform: translate(-.5em, -50%);
}

[tooltip][flow^="left"]::after {
    top: 50%;
    right: calc(100% + 5px);
    transform: translate(-.5em, -50%);
}

Right:

[tooltip][flow^="right"]::before {
    top: 50%;
    border-left-width: 0;
    border-right-color: #333;
    right: calc(0em - 5px);
    transform: translate(.5em, -50%);
}

[tooltip][flow^="right"]::after {
    top: 50%;
    left: calc(100% + 5px);
    transform: translate(.5em, -50%);
}

7. Animate All the Things

Animations are amazing. Animations can:

  • help users feel comfortable
  • help users with the spacial awareness of your UI
  • call attention to things that need to be seen
  • soften elements of a UI that would otherwise be a binary on/off jarring effect

Our tooltips will fall into that last description. Rather than having a text bubble pop into existence and pop out in a flash, let’s make them softer.

@keyframes

We’ll need two @keyframe animations. The up/down tooltips will use the tooltips-vert keyframe, and the left/right tooltips will use the tooltips-horz keyframe. Notice in both of these keyframes we are only defining the desired ending state of the tooltips. We don’t need to know where they come from (the tooltips themselves have that style information). We just want to control where they go to.

@keyframes tooltips-vert {
  to {
    opacity: .9;
    transform: translate(-50%, 0);
  }
}

@keyframes tooltips-horz {
  to {
    opacity: .9;
    transform: translate(0, -50%);
  }
}

Now, we need to apply these keyframes to the tooltips when a user hovers over the triggering elements (the elements with the [tooltip] attributes). Since we are employing various flows to control how the tooltips will show, we need to identify those possibilities in the styles.

Use :hover to Pass Control to Animations

[tooltip]:not([flow]):hover::before,
[tooltip]:not([flow]):hover::after,
[tooltip][flow^="up"]:hover::before,
[tooltip][flow^="up"]:hover::after,
[tooltip][flow^="down"]:hover::before,
[tooltip][flow^="down"]:hover::after {
    animation: 
        tooltips-vert 
        300ms 
        ease-out
        forwards;
}

[tooltip][flow^="left"]:hover::before,
[tooltip][flow^="left"]:hover::after,
[tooltip][flow^="right"]:hover::before,
[tooltip][flow^="right"]:hover::after {
    animation: 
        tooltips-horz 
        300ms 
        ease-out 
        forwards;
}

Remember we cannot animate the display property, but we can give the tooltips a fade-in effect by manipulating the opacity. We are also animating the transform property which gives the tooltips a subtle movement as if they are flying in to point at their triggering elements.

Notice the forwards keyword in the animation declaration. This tells the animation to not reset once it completes, but to proceed forward and stay at the end.

Conclusion

Fantastic job! We covered a lot in this tutorial, and now have a neat collection of tooltips to show for our hard work:

We’ve only scratched the surface of what can be done with CSS tooltips. Have fun playing with them and keep on experimenting, and concocting your own recipes!

More CSS UI Tutorials