Using tooltips can be a really simple way to improve the user interface of your website. Tooltips help to 'declutter' the page by displaying contextual information only at the moment it's needed. In this post I'll cover how to implement simple tooltips using Rails' helper methods, some CSS, and a sprinkle of javascript/jquery. The implementation results in a total of less than 55 lines of code. This is a great example of making a modular component which you can then use whenever needed across your entire web app. Modularity begets maintainability.

You'll need to download jQuery if you haven't already to get a critical part of these modular tooltips working, but not to worry, we'll only use it for a total of 6 lines. Here's what you'll be able to do after following this short tutorial:

Calling this in your view:

<%= tooltip_for("Show All Filters") do %>
    <%= image_tag 'show_all_filters.svg', id: "show_all_filters" %>
<% end %> 

Will output this:

A simple tooltip

Now that you know what you're in for, let's get started. Open up application_helper.rb in your Rails project directory. We'll first implement the tooltip_for helper method which sets up the html for our tooltip:

In application_helper.rb:

def tooltip_for(text, &block)
    content = capture(&block)
    content_tag :div, class: "tooltip_wrapper" do
      content + content_tag(:div, content_tag(:div, "", class: "tooltip_triangle") + text, class: "helper_tooltip")
     end
  end

Here we pass the tooltip text and the block as arguments to the helper method. The first line extracts the contents of the block that was passed in using Rails' capture() helper method and assigns it to the content variable. The block form of rails' content_tag helper method is then used to output a div which will wrap the content that the tooltip is being applied to.

Nested within that wrapper element is the content itself and chucked in there alongside it is the actual tooltip, its associated CSS triangle, and the text which will be displayed to the user. You'll notice we're now passing the tooltip triangle div and text parameter as the second argument to the "helper_tooltip" content_tag call, instead of using the block form seen previously to contain the contents.

Now let's move on to the CSS.

In tooltips.css:

.tooltip_wrapper {
  position: relative;
}

.helper_tooltip {
    position: absolute;
    background: #444;
    padding: 2px 5px 2px 5px;
    bottom: 100%;
    color: #fff;
    display: inline-block;
    margin-bottom: 15px;
    z-index: 300;
    opacity: 0;
    white-space: nowrap;
    pointer-events: none;
    -webkit-box-shadow: 2px 2px 6px rgba(0, 0, 0, 0.28);
       -moz-box-shadow: 2px 2px 6px rgba(0, 0, 0, 0.28);
        -ms-box-shadow: 2px 2px 6px rgba(0, 0, 0, 0.28);
         -o-box-shadow: 2px 2px 6px rgba(0, 0, 0, 0.28);
            box-shadow: 2px 2px 6px rgba(0, 0, 0, 0.28);
}

.tooltip_triangle {
    position: absolute;
    border-left: solid transparent 10px;
    border-right: solid transparent 10px;
    border-top: solid #444 10px;
    bottom: -10px;
    content: "";
    height: 0;
    width: 0;
}

.tooltip_wrapper:hover .tooltip_triangle {
    opacity: 1;
    pointer-events: auto;
}

.tooltip_wrapper:hover .helper_tooltip {
    opacity: 1;
    pointer-events: auto;
}

Here we're applying CSS rules to the three elements produced by our tooltip_for helper method. The tooltip_wrapper has position:relative applied so that we can absolutely position our child tooltip regardless of what css rules the parent containing element (be it a li, another div, etc.) has applied to it. This is the very common absolute positioning inside relative positioning CSS convention. If you're not familiar with this, take a gander over at CSS Tricks.

For the helper tooltip, there are a few important things to note. One is that it's offset from the bottom by 100%, meaning we'll see the tooltip at the top of the element and not to the left, right or bottom. On top of this, it has a margin-bottom of 15px to allow space for the tooltip triangle, which will be positioned just below the tooltip. We also give it a high z-index value so it shows above all other elements, an opacity of 0 so that it's hidden initially, a white-space: nowrap rule to ensure the tooltip text doesn't break onto two lines, absolute positioning as discussed above, and pointer-events: none to prevent the tooltip from obstructing the user's ability to click a link, button etc. which may be partially or completely obscured by the tooltip. The other lines are just common-place styling rules.

Then we have our tooltip triangle. For an explanation of CSS triangles, again take a peek over at CSS Tricks.

Lastly, we want to actually display the tooltip when the user hover's over the element. Since the contents to which the tooltip is being applied are now conveniently contained by our .tooltip_wrapper div, we can just set a CSS rule using the :hover pseudo-element on that wrapper, select the helper_tooltip as it's child element, then change it to full opacity and switch back to its default pointer-events behavior. Now we don't have to mess with making new CSS wrapper elements every time we want a tooltip somewhere!

If you're following along you'll notice that no tooltip is (or should be) the same as another one. They're all of varying widths, some longer than others. This dynamic aspect can throw a wrench in the tooltip's positioning relative to the element the tooltip is being applied to, thereby "uncentering" the tooltip. For this reason, we'll throw in some sexy jQuery to calculate the width of the tooltip and the width of the element, and then center both the triangle and the tooltip in relation to the element using those values.

In tooltips.js:

$(document).ready(function() {

// General helper function for displaying tooltips
function ShowTooltip(selector) {
    $(selector).mouseover(function() {
        var tooltip_width = $(this).find(".helper_tooltip").width();
        var elem_width = $(this).width();
        $(".helper_tooltip .tooltip_triangle").css("margin-left", "" + (tooltip_width/2 - 10) + "px");
        $(".helper_tooltip").css("margin-left", "-" + (elem_width/2 + tooltip_width/2) + "px");
    });
}
ShowTooltip(".tooltip_wrapper");
});

Astute eyes will notice that seemingly 'magic number' -10 thrown in there. That's actually simply accounting for the width of the tooltip_triangle div and adjusting its left-margin in accordance.

And there you go, simple tooltips in a reusable, modular manner. Just call this in your view and you're all set:

<%= tooltip_for("Your tooltip text here") do %>
    <%= Your content for the tooltip here (link, image, icon, button, text, etc.) %>
<% end %> 

As a side note, it would be useful to have the option to specify the positioning of the tooltip (left, right, bottom or top of the element), and also the option of calling the tooltip_for helper method in non-block form for simpler use cases. Perhaps I'll try my hand at this some other time, though.

Comments? Questions? Discuss over on HN.