DEV Community

Cover image for Building a tooltip with only CSS
Nathan Kallman
Nathan Kallman

Posted on • Updated on • Originally published at kallmanation.com

Building a tooltip with only CSS

There's a plenty of libraries (mostly JavaScript) to enable the classic tooltip design pattern. But let's build our own! And let's not pollute our HTML with unnecessary markup and avoid loading JavaScript while we're at it.

The goals

  1. Show an additional snippet of text when hovering.
  2. Style in the classic triangular tabbed bubble.
  3. Allow delayed/animated appearance.
  4. Allow tooltip in each of the four directions.
  5. Require no additional DOM nodes and as little config in HTML as possible.
  6. Require 0 JavaScript.

The results

How to get there:

The CSS selectors used

  1. Attribute selector
    a. [attrubute] - selects an element with attribute (regardless of attribute's value)
    b. [attribute^="prefix"] - selects an element with attribute where the value starts with prefix
    c. [attribute$="postfix"] - selects an element with attribute where the value ends with postfix

  2. Psuedo-class :hover - selects an element that has a mouse cursor hovering over it

  3. Psuedo-elements
    a. ::after - styleable element that doesn't actually exist in the DOM; considered the last child of the selected element
    b. ::before - styleable element that doesn't actually exist in the DOM; considered the first child of the selected element

Important CSS styles used

  1. content - used in ::before and ::after psuedo-elements to set their content

  2. attr("attribute") - accepts an argument attribute, and can be used to set the content of a psuedo-element based on the value of said attribute on the element selected

  3. position (along with top, left, right, bottom, and transform) - Used to position the tooltip

  4. calc() - accepts a basic mathematical expression and calculates the final value (what's useful to us is how we can mix units, like width: calc(40px + 50%) which will give us the width to 50% of what it could be plus a constant 40 pixels extra)

Starting out: the HTML

On any HTML tag (span, a, div, ...) let's add two attributes: one to describe which direction we want the tooltip to appear in and one to specify the content of the tooltip. Let's use aria-label to contain the content and data-tooltip for the tooltip configuration.

<any-tag aria-label="Tooltip Content" data-tooltip="up" >Normal Content</any-tag>
Enter fullscreen mode Exit fullscreen mode

Starting out: the core tooltip CSS

The tooltip will need a couple things:

  1. The content we put in aria-label
  2. To be positionable based on the element it is attached to.
  3. To be invisible until hovered

To select our element we can use the attribute selector (at this point it doesn't matter what the content is), let's select elements that have both of our tooltip attributes [data-tooltip][aria-label].

On the element itself, we need a position: relative. This is so when we go to position: absolute the psuedo-element that is our tooltip it will position itself based on the element and not some other parent.

Finally, to hide the tooltip, let's put both opacity: 0 and visibility: none. (Hiding can also be done with display: none, but that cannot be transitioned nicely the way opacity + visibility can)

[data-tooltip][aria-label] { 
  position: relative;
}
[data-tooltip][aria-label]::before {
  content: attr(aria-label);
  position: absolute;
  opacity: 0;
  visibility: none;
}
Enter fullscreen mode Exit fullscreen mode

note that the codepen above has extra styles for color and spacing, but these are the important parts for making the tooltip work

Positioning the tooltip: Up/Down

Now we should select based on particular configuration in the data-tooltip attribute. We can select where the value starts with something by [data-tooltip^="startswiththisvalue"] (we'll select by the starting value instead of exact value to leave room in the attribute for configuring other things later). We want to do two things for the Up/Down direction of tooltips:

  1. Center horizontally
  2. Position directly above/below the element plus a small margin (to leave room for the common triangular tab on nice tooltips)

Placement above is simple enough: we will position ourselves from the bottom of the element, 100% (that is, place the bottom of the tooltip 100% of the elements height away from the bottom of the element), and add the small margin using calc(). And for below, the same principle applies, but we use top instead of bottom.

[data-tooltip^="up"][aria-label]::before {
  bottom: calc(0.5rem + 100%);
}
[data-tooltip^="down"][aria-label]::before {
  top: calc(0.5rem + 100%);
}
Enter fullscreen mode Exit fullscreen mode

Centering the tooltip can be thought of as two distinct steps:

  1. Place the tooltip's right edge at exactly the center of the element
  2. Translate the tooltip to the right exactly half of its own width

And we will do just that with two styles: right: 50% (which places the right edge of the tooltip in the center of the element) and transform: translateX(50%) which moves the tooltip from that position to the right by 50% of its own width. We'll add this to both Up/Down:

[data-tooltip^="up"][aria-label]::before {
  bottom: calc(0.5rem + 100%);
  right: 50%;
  transform: translateX(50%);
}
[data-tooltip^="down"][aria-label]::before {
  top: calc(0.5rem + 100%);
  right: 50%;
  transform: translateX(50%);
}
Enter fullscreen mode Exit fullscreen mode

Positioning the tooltip: Left/Right

Positioning left and right uses the same concepts as above, but instead of centering vertically we want to center horizontally and instead of positioning at the top and bottom of the element we want to position at the left and right.

[data-tooltip^="left"][aria-label]::before {
  right: calc(1rem + 100%);
  bottom: 50%;
  transform: translateY(50%);
}
[data-tooltip^="right"][aria-label]::before {
  left: calc(1rem + 100%);
  bottom: 50%;
  transform: translateY(50%);
}
Enter fullscreen mode Exit fullscreen mode

Making the tooltip appear: without delay

The last new piece of CSS we need is the Psuedo-class :hover. As the name implies, this will select an element that has a pointer hovering over it (just when we want to display the tooltip!). And all we need to do is change it from the default invisible style we gave it above to a visible style:

[data-tooltip][aria-label]:hover::before {
  visibility: visible;
  opacity: 1;
}
Enter fullscreen mode Exit fullscreen mode

And we now have a totally functional (if a little ugly) CSS-only tooltip! You can stop here or keep going to add some niceties you would expect of a tooltip: delay, gentle transition, triangular-tab...

Bonus points: static delay + transition

We can use the transition-* styles to delay when the tooltip appears and gently fade in:

[data-tooltip][aria-label]:hover::before {
  visibility: visible;
  opacity: 1;
  transition-property: opacity;
  transition-duration: 0.2s;
  transition-timing-function: ease-in-out;
  transition-delay: 0.5s;
}
Enter fullscreen mode Exit fullscreen mode

transitioning on the opacity gives a nice fade in effect. A duration of 0.2 seconds should make it quick enough to avoid being annoying. And we can set a delay to not immediately show the tooltip (avoids popping information in and out while users move their mouse across the page; also referred to as "hover intent").

Bonus points: configurable delay + transition

We can do even better! Let's allow the element to configure how long the delay (if any) the tooltip should have. Unfortunately we can't dynamically allow any value in transition-delay (attr() only works in the content style) so we will have to select a few predetermined delay times.

[data-tooltip][aria-label]:hover::before {
  visibility: visible;
  opacity: 1;
  transition-property: opacity;
  transition-duration: 0.2s;
  transition-timing-function: ease-in-out;
  transition-delay: 0s;
}
[data-tooltip$="500"][aria-label]:hover::before {
  transition-delay: 0.5s;
}
Enter fullscreen mode Exit fullscreen mode

Here I've used the ending portion ($=) of the data-tooltip attribute to set the delay (in milliseconds). I hope it is clear how this would be extended to add delays of 0.1 seconds up to 1 second and beyond.

Bonus points: triangular tabs and other styles

I will actually leave this as an exercise to you, dear reader. (Or you'll just copy from the codepen I guess...). But I will give you hints:

  1. You probably need another element to create the triangle; you've already used ::before, thankfully there's also ::after...
  2. You need to position/show/hide/transition/delay the triangle the same way as the tooltip proper.
  3. A triangle can be made using borders on a no-width, no-height element. Think about what it would look like if you made thick borders around that element and gave each one a different color (also remember that "color" can mean transparent)

Alt Text

Wrapping up and side notes

Thanks for reading this far! I hope you learned something or at least enjoyed watching me battle the CSS dragon.

I like sharing and finding things like this where common web design patterns can be done without a massive JavaScript library or framework bogging the page down. Give me some suggestions below on patterns you'd like to see me break-down in CSS/HTML-only and maybe this can become a series.

A side note on selection of aria-label. This is fine and keeps things relatively accessible, but the better option is title. This is the most native "tooltip" there is. However, there's no styling options available (even just disabling the default effect, as we could then disable and do what we've done here to make the tooltip). Since we can't disable the default effect, it would make our tooltip appear twice (once the way we styled it to be, and once in the default way).

Latest comments (1)

Collapse
 
grahamthedev profile image
GrahamTheDev • Edited

Sadly these are not accessible, although I am glad you did at least consider people with disabilities so you get a big ❤ from me!

Firstly you have a big error with your aria usage I am afraid.

If you set the aria-label on something then that is what is read out, it effectively cancels out the content inside.

So <span data-tooltip="right 100" aria-label="Brown fox">Right</span> will actually read out "Brown Fox" not "Right, Brown Fox" as intended.

Also keyboard users (people with mobility impairments being a prime example) have no way of accessing your tooltip content as it is not focusable.

Finally anyone who use a screen magnifier will struggle using your tooltip as you have to have the mouse positioned over the original text for it to work and they may need to move their cursor over the tooltip itself if they have a particularly high zoom level, at which point it becomes invisible again.

You may find this answer I gave on stack overflow helpful, but sadly we have to use JS in my implementation. With a slight adjustment the concept could be used for a tooltip.

Things to Consider

There are a few things to consider here:

  1. Is a question mark sufficiently clear that it is help for the field? I would say no but I will still use it in this example, I would encourage you to use actual words for people with cognitive disorders…