Grow your CSS skills. Land your dream job.

Bubble Point Tooltips with CSS3 & jQuery

Published by Chris Coyier

I needed some tooltips for a thing. In looking around for a little inspiration, I didn't have to go further than my dock:


The inspiration: contextual menus from OS X dock

This is where I ended up:


View Demo   Download Files

The HTML: Keepin' it Clean

Links can have tooltips in HTML with no fancy coding whatsoever. Just give a link a title attribute.

<a href="#" title="Hi, I'm a tooltip thingy.">link</a>

Then when you hover over that link a second or two, you'll get the yellow box thingy:


Default tooltip in pretty much any browser.

Let's not mess with that. Our HTML will be exactly what is shown above.

The thing is, that yellow box thingy is absolutely unstyleable. Maybe someday it will be once shadow DOM stuff like that is specced out, but today, it's not possible in any browser. So we're gonna run some JavaScript which is going to yank out that attribute and make <div>'s with the same text, which we'll position/hide/show as needed.

The JavaScript: jQuery Plugin

As usual around these parts, we'll use jQuery. This is the perfect kind of thing to make into a plugin. You might want to use this functionality on an arbitrary collection of elements of your own choosing, as well as chain it, so the plugin pattern is ideal.

Calling it will be as simple as:

$("article a[title]").tooltips();

That will tooltip-ize all links with titles that happen to be inside <article> elements. You could make that selector whatever you want.

Our plugin will do this:

  1. Loop through each link
  2. Create a (hidden) div.tooltip for each of them, the text inside matching that links title attribute.
  3. Remove the links title attribute (only way to get rid of default yellow box popup
  4. When link is hovered on...
  5. Position the tooltip accordingly and slide it into visibility.
  6. When link is hovered off...
  7. Slide link out of visibility

Massive code dump, commented for your pleasure:

// IIFE to ensure safe use of $
(function( $ ) {

  // Create plugin
  $.fn.tooltips = function(el) {

    var $tooltip,
      $body = $('body'),
      $el;

    // Ensure chaining works
    return this.each(function(i, el) {

      $el = $(el).attr("data-tooltip", i);

      // Make DIV and append to page 
      var $tooltip = $('<div class="tooltip" data-tooltip="' + i + '">' + $el.attr('title') + '<div class="arrow"></div></div>').appendTo("body");

      // Position right away, so first appearance is smooth
      var linkPosition = $el.position();

      $tooltip.css({
        top: linkPosition.top - $tooltip.outerHeight() - 13,
        left: linkPosition.left - ($tooltip.width()/2)
      });

      $el
      // Get rid of yellow box popup
      .removeAttr("title")

      // Mouseenter
      .hover(function() {

        $el = $(this);

        $tooltip = $('div[data-tooltip=' + $el.data('tooltip') + ']');

        // Reposition tooltip, in case of page movement e.g. screen resize                        
        var linkPosition = $el.position();

        $tooltip.css({
          top: linkPosition.top - $tooltip.outerHeight() - 13,
          left: linkPosition.left - ($tooltip.width()/2)
        });

        // Adding class handles animation through CSS
        $tooltip.addClass("active");

        // Mouseleave
      }, function() {

        $el = $(this);

        // Temporary class for same-direction fadeout
        $tooltip = $('div[data-tooltip=' + $el.data('tooltip') + ']').addClass("out");

        // Remove all classes
        setTimeout(function() {
          $tooltip.removeClass("active").removeClass("out");
          }, 300);

        });

      });

    }

})(jQuery);

Couple things to note: 1) The final demo is going to have the tooltips animated a bit. None of that happens here in the JavaScript. Animations are design. Design is CSS. Thus, we do all that in the CSS. 2) One little design touch on these is that the tooltips slide in and slide out the same direction. In CSS, by just adding and removing a single class and using transitions, that's not really possible. The transition will run in reverse upon the classes removal and thus will slide out same direction it came in. By using a setTimeout, we can apply a temporary class, and animate that as well. Any more elegant ideas for that, let me know in the comments below.

A Semantic Bummer?

Adding divs to the bottom of the document feels like a bummer; just doesn't feel very semantic. They aren't connected to the links they came from in any meaningful way through HTML alone.

Also removing the title tag doesn't sit quite right with me. I've heard they don't do much for accessibility anyway, but still. I wish preventDefault() would prevent that from showing, but it does not.

Any ideas on that stuff, let's hear 'em in the comments below.

The CSS: Close Counts

Notice in the OS X screenshot at the top of this article, the background and borders themselves are a bit transparent. I was unable to achieve that here. Those things are possible in general, but the pointy arrow down is an additional element and how the borders and backgrounds connect to each other is just through overlapping and it looks bad with any transparency at all. So, solid colors, NBD.

Oftentimes pointy arrows like we have here are doable with no additional markup or images by using pseudo elements and CSS triangles. In our case, we are going to use a pseudo element, but we'll need a real element as well. The real element (the <span> that the JavaScript inserted in each tooltip <div>) serves as a positional box and does the cropping. The pseudo element is the actual pointer. A box styled the exact same way as the tooltip, only rotated 45deg and cropped out by it's parent. Here's the schematics:


The thing goes in the thing

And so:

.tooltip, .arrow:after {
  background: black;
  border: 2px solid white;
}

.tooltip {
  pointer-events: none;
  opacity: 0;
  display: inline-block;
  position: absolute;
  padding: 10px 20px;
  color: white;
  border-radius: 20px;
  margin-top: 20px;
  text-align: center;
  font: bold 14px "Helvetica Neue", Sans-Serif;
  font-stretch: condensed;
  text-decoration: none;
  text-transform: uppercase;
  box-shadow: 0 0 7px black;
}
.arrow {
  width: 70px;
  height: 16px;
  overflow: hidden;
  position: absolute;
  left: 50%;
  margin-left: -35px;
  bottom: -16px;
}
.arrow:after {
  content: "";
  position: absolute;
  left: 20px;
  top: -20px;
  width: 25px;
  height: 25px;
  -webkit-box-shadow: 6px 5px 9px -9px black,
                      5px 6px 9px -9px black;
  -moz-box-shadow: 6px 5px 9px -9px black,
                   5px 6px 9px -9px black;
  box-shadow: 6px 5px 9px -9px black,
              5px 6px 9px -9px black;
  -webkit-transform: rotate(45deg);
  -moz-transform:    rotate(45deg);
  -ms-transform:     rotate(45deg);
  -o-transform:      rotate(45deg);
}
.tooltip.active {
  opacity: 1;
  margin-top: 5px;
  -webkit-transition: all 0.2s ease;
  -moz-transition:    all 0.2s ease;
  -ms-transition:     all 0.2s ease;
  -o-transition:      all 0.2s ease;
}
.tooltip.out {
  opacity: 0;
  margin-top: -20px;
}

Note the .active class is what does the slide in animation and the .out class does the slide out.

Thanks

To Adrian Adkison for helping with some ideas and code along the way.

Fair Warning about Opera

Opera doesn't support pointer-events in CSS, so this whole demo will appear not to work. That's because the tooltips are actually positioned right on top of the links with just their opacity turned down to 0. If you want to fix it up in Opera, you'll need to literally display none/block them in the JavaScript, or use CSS to move them off the links far enough they don't impede clickability. If someone wants to make that change but have it otherwise function exactly as this demo is, I'm down to update it.

Demo / Download

View Demo   Download Files

Comments

  1. Permalink to comment#

    MMM looks interesting! :)

  2. Permalink to comment#

    Interesting.
    Anyway I would like to point out that it doesn’t work on IE8: the tooltips appear all together. In the first tooltip there’s the  after the heart – don’t know if it’s meant or not ;)

    • To deal with IE 8, you could:

      <!--[if !IE | (gt IE 8)]><!-->
      <script src="js/tooltips.js"></script>
      <script>
      	$(function() {
      		$("#page-wrap a[title]").tooltips();
      	});
      </script>
      <!--<![endif]-->
  3. Permalink to comment#

    Looks and works awesome, I can see this coming in useful, thanks!

    Also, your comment form is awesome, I’ve been clicking in and out of the text area for about five minutes now.

  4. One thing I’m worried about is how the accessibility of the links is affected when you remove the title attribute. Since that information is meant to provide extra guidance and meaning to the link, removing it to another element seems like it might make the piece less accessible to some users.

    (Though if users with devices for accessibility don’t have JavaScript, they won’t lose anything.)

  5. SFdude
    Permalink to comment#

    it doesn’t work on FF 3.6.24 (= latest),
    under XP-Pro SP3 (fully updated).

    No pop ups appear at all,
    when hovering or even clicking
    on the Demo links!.. :-(

  6. Bert de Vries
    Permalink to comment#

    tooltip.js is never loaded in any browser except IE > 8?

    
    	<!--[if gt IE 8]>
    	<script src="js/tooltips.js">
    	<script>
    		$(function() {
    			$("#page-wrap a[title]").tooltips();
    		});
    	</script>
    	<![endif]-->
    
  7. Permalink to comment#

    Looks like you’re having some compatibility issues. Well, when you address them, I’ve got a suggestion for the removing the title attribute.

    Instead of removing it when you set up your divs, why not remove it when the link is moused over, then add it back on mouse leave. That way you could still keyboard focus and see the title (for accessibility), but when using the mouse you’d get the visual upgrade.

  8. Permalink to comment#

    One thing I definitely don’t like about it is that I can’t hover over the tooltip itself once it appears. I’m out of luck if I want to copy the tooltip text.

  9. Okay it works now on Firefox. Guys, do a hard refresh on the page as it might be cached. COMMAND+SHIFT+R on Firefox.

  10. Permalink to comment#

    A very easy way to do that is using the Twitter Bootstrap CSS library. http://twitter.github.com/bootstrap/

  11. Ben
    Permalink to comment#

    I’m surprised you did this with JavaScript instead of pseudo elements , pseudo classes, and content:

    • Pseudo elements are involved. But, yeah, there are ways to do styled tooltips with ONLY CSS and no JavaScript. But it’s far more limited. For example, I wanted to use the title attribute as tooltips naturally have. Can’t do that without some JavaScript, as there is no way to prevent the default yellow popup. You also can’t get the exact shape with arrow and borders with only pseudo elements. You also can’t get the same-direction movement. So yeah, I went with JS to get all that.

  12. Permalink to comment#

    one word “WOW”

  13. Permalink to comment#

    Problem I’ve been having is that it breaks pretty substantially on mobile. It requires a click to open which is understandable, but there is no way to close it. This is a big problem because it covers everything underneath and you can’t get to it.

    Is there a way to remove a hover state on the second click? I haven’t figured out a way yet.

  14. Permalink to comment#

    Great code, but it jQuery and problems with my browser Opera ;/. Nevermind, hope that will be soon possible on html 5 :)

  15. Permalink to comment#

    Great write up. One alternative for the tip pointer to make it compatible in browsers that don’t support rotation: use a CSS “border triangle” using something like this (not tested):


    .arrow {
    width: 0;
    height: 0;
    border-left: 20px solid transparent;
    border-right: 20px solid transparent;
    border-top: 20px solid #fff;
    }

    .arrow:after {

    width: 0;
    height: 0;
    border-left: 20px solid transparent;
    border-right: 20px solid transparent;
    border-top: 20px solid #000;
    content: '';
    position:absolute;
    top: -2px;
    }

    Although probably not as crisp as a rotated div, it would work in IE7+.

  16. stryju
    Permalink to comment#

    i think it would be more semantically-friendly if tool-tips were added only when needed ( on hover )

    quick example:

    http://jsfiddle.net/stryju/Ygn42/

    this way You don’t pollute the document with random tool-tip elements AND can remove the title ONLY when needed ( on tool-tip appearance )

    • Permalink to comment#

      @stryju: Your code is very very nice and neat — this is definitely an opportunity to use the .data() function in jQuery. Well done.

    • stryju
      Permalink to comment#

      thanks!

      updated it a bit, so it has proper position and “crispy arrow” ;-)

    • I like that idea. I think there might be advantages doing it either way. With my way all the dom manipulation happens up front, so possibly slower execution time right away, whereas with yours there is dom manipulation with every hover, so slower throughout usage of the page.

    • stryju
      Permalink to comment#

      minimal DOM manipulation – only the handlers are attached, no DOM manipulation done = better performance imo

      i think the big + of this approach is: no dirty DOM ( why to keep tool-tips in DOM if they can never be used? ).

      If u want to pollute the DOM, why to create X number of tool-tips, not one and then give it the text ( repaint will happen anyways )?

      1 tool-tip, text changed on hover = cleaner DOM

      correct me if i’m wrong here ;-)

  17. Permalink to comment#

    “Animations are design. Design is CSS.”

    I’m not agreeing. There is a certain appetite for the web devs to mess things up and muddy the waters on the normal meaning of words, that’s going on for years now.

    This one is no different. Design is design. Animation is action, an action that you have to design *too*.

    And I’m pretty sure everyone that knows or have heard just vaguely about cartoons or animations, will tell you the same thing: animation is not design, is something that design is applied to. Web dev is no different.

  18. Adrian Adkison
    Permalink to comment#

    I thinks the “<” at the beginning of the selector does not need to be there:

    $tooltip = $(‘<div[data-tooltip=’ + $el.data(‘tooltip’) + ‘]’).addClass(“out”);

  19. Permalink to comment#

    Really nice, but if it don’t work on old browsers, limit its use. Always is the same problem ;(

  20. Sandeep Paul
    Permalink to comment#

    Good one. I recently build created a jQuery based tooltip similar way. The one I build had to show html content. Also at any point there will be only one in the dom. I replaced the content of this div with the new content.

  21. Permalink to comment#

    It would be nice if user can click a menu item or link in the popover

    • You could alter this to do it’s actions on click rather than hover. Just wasn’t the goal here.

      Another point to be made though is that the tooltip div is not a descendant of the anchor link. If that was the case, by default we would get better behavior. The tooltip wouldn’t go away if you moused over it, allowing you to do things like copy the text of it. But unfortunately even though you can put divs instead a’s in HTML5, the behavior doesn’t work quite right in this case.

  22. Great tutorial its helped me a lot i used this tool tip in many web sites.

  23. Helen
    Permalink to comment#

    Works fine now.

  24. Permalink to comment#

    Eyeballing the code, it looks like there’ll be some conflicts if you call tooltips() twice using different selectors. i.e multiple tooltips will appear on hover as there’ll be multiple divs with the same data-tooltip value.

  25. Permalink to comment#

    Pretty cool, but like some others have already mentioned, you could do this all with CSS. I actually had a challenge like this in one of my recent projects. Check out this fiddle: http://jsfiddle.net/r7Q9m/

    Works in IE8+, doesn’t rely on the -webkit-transform for the triangle (though you don’t get the border around the tooltip) but still – it works, and well.

    I get that the jQuery means we can have animation and drop the default behavior of the title attribute, but is that really a great thing? Sure the yellow text might be annoying, but with the CSS-only technique it’s just a dash of preventDefault and hey presto – simple, CSS-only tooltips that work in IE8+, and the default title behavior for older browsers.

    • Zsitro
      Permalink to comment#

      Wonderful!!!

    • Very clean and simple, I like it.

      Just to be clear, you said ” you could do this all with CSS” referring to my example, which is not true.

      – You can’t get the same-direction sliding
      – You can’t remove the yellow popup (even your example has that, preventDefault does not work)
      – You can’t get shadowed/bordered triangles

      So yeah, awesome demo, love it, just don’t want to be clear that I wasn’t using JavaScript extraneously, I had some things I wanted to do that it’s the only way.

  26. Chris, this line sticks out for me.

    “Animations are design. Design is CSS.”

    I know this is slightly irrelevant, but I have always held this same opinion, much to the usual disagreement of others. So it’s nice to know somebody with the standing and reputation you have shares the same view.

  27. Permalink to comment#

    nice, looks like iPhone’s tooltip

    however, can it be placed by single PNG image with rectangular and downarrow shape rather than creating the tooltip CSS

    (considering that browser can display interlaced image from PNG)

  28. DeltaDave
    Permalink to comment#

    I had to add a z-index to tooltip to ensure it was always on top.

  29. Permalink to comment#

    Oh, great!!! :(o)

  30. Permalink to comment#

    Thank you . Great Jop :)

  31. Permalink to comment#

    Interesting how my comment does not show up. It seems that you have too many valid comments going to your spam folder.Anyway:

    I noticed that the tooltip did not work for IE8 (which I saw your fix in the comments).
    Also for IE9 I noticed that the tooltip is not consistent, meaning that the tooltip shows in one link but not others. Have you noticed this behavior.
    For Safari, Google Chrome and Mozilla (all latest versions), the tooltip works great.
    Thanks.

  32. Permalink to comment#

    Great, I want to use this for my website.

  33. Jai
    Permalink to comment#

    This is not work in IE.

  34. manuakasam
    Permalink to comment#

    Take a look at the following Fiddle:

    http://jsfiddle.net/r7Q9m/

    You can create the tooltip with relying on CSS3 only. It mainly depends on if you want to rely on CSS3 or on JavaScript. Probably with the majority relying on JavaScript would result in a higher degree of support.

    Using the Pseudo-Elements from “:before” and “:after” for once the content and second the “pin” is enough, too.

  35. Awesome Post! I’m implementing on my own site now!

  36. Permalink to comment#

    Not work in Internet Explorer.

    • It doesn’t work at all in IE 8 and down, so use conditional comments to prevent the scripts from being loaded there (see early comment).

      Also read the section about Opera in the article. The problem is pointer-events. You can alter this yourself to move the “hidden” tooltip from sitting on top of the link to solve that problem. For instance, have it be above the link instead and float down and away instead of up and away.

  37. Eva
    Permalink to comment#

    Awesome and very useful!

    One question: Is it possible to prevent the tooltip from hovering out of the browser window.

    THX!

  38. Permalink to comment#

    I was having trouble with the tooltips positioning relative to the wrap container. Pulled my hair out trying to figure it out, played with ‘relative’ and ‘absolute’ all over the place. I finally resorted to changing the var linkPosition = $el.position(); to var linkPosition = $el.offset(); even though I don’t think this is recommended.

    Also, can anyone tell me why this doesn’t work in IE8? I added all the IE specific ‘opacity’ rules, but still doesn’t work at all. It doesn’t look like anything that unusual going on with the JS or CSS, so it can’t be that difficult to at least make a crippled version work in IE8.

  39. Ricardo
    Permalink to comment#

    Using a span inside of the a element, and positioning it as absolute and show it when hovered would be semantic right?

  40. Tuomas Salo
    Permalink to comment#

    Looks good, but is a bit problematic. The contents of the title attribute is normally plaintext, but this solution turns it into HTML – but not for everyone, since the plaintext title is used as a failover. So, if you want to have an ampersand in the title text, or maybe even a <tag>, what would you write to the title attribute?

    Thus, this tooltip implementation is also a possible attack vector for XSS exploits. Imagine an application that lets users input some text for tooltips, say, image captions. Then the admin wants to fancify the interface with these tooltips. Whoops! Now the users can enter arbitrary HTML code.

    My proposition: let the title attribute be plain text. Add another attribute, say data-fancy-html-title when HTML is needed.

    • Permalink to comment#

      Or supply callback for the tooltip contents. Let user decide what goes in the tooltip – if rich text or plain text… I’ve done it with my Elixon Tooltip and it is as universal as it can be… I can use it for so many things now…

  41. Daniel Piechnick
    Permalink to comment#

    It’s a lot of CSS for a little tooltip. I’ve made tooltips in the past, but I always end up making them redundant, by rewriting my content, and just explaining things better. which is sad, because I really like tooltips. :P

    Daniel Piechnick

  42. Paul
    Permalink to comment#

    That one doesn’t reposition itself if theres no space in the screen… I’m trying to make one but I think this one is the most efficient and simple:

    https://code.google.com/p/littletip/

    Thanks for the post!

    • That would be a nice feature. Not too hard actually, if you use jQuery UI for .postion() instead of native jQuery. .postion() has collision detection and will move what what you are positioning to be on screen.

  43. Noha
    Permalink to comment#

    How to make this tooltip at the bottom? any one help plz

  44. ahmed
    Permalink to comment#

    how to make popup stay before mouse out on it ?

  45. Happy Chhor Lyheang

    TypeError: $(…).tooltips is not a function

This comment thread is closed. If you have important information to share, you can always contact me.

*May or may not contain any actual "CSS" or "Tricks".