skip to content

Tooltips — affectionately misnomered as "tootlips" by my friend Steve — are a precariously longstanding interface pattern. Literally "tips for tools", they are little bubbles of information that clarify the purpose of otherwise ambiguous controls/tools. A common example is a control that is only represented by a cryptic icon, the meaning of which the user has yet to learn.

When and how these bubbles transpire is apparently up for debate, since I've encountered many a tooltip and they all seem to behave slightly differently. I've come to the conclusion that these numerous implementations fall into two distinct groups: true tooltips, and a pattern I'm hesitantly calling the "toggletip", coined by the aforementioned Steve in some research and experimentation he did not long ago.

Inclusive design is often about providing the user with the right tool for the job, and the right kind of tooltip to go with that tool. In this article, I'll be looking at situations which might call for a tooltip or else a toggletip, and formulating inclusive implementations for each.


The title attribute

We can't talk about tooltips without bringing up the title attribute: the HTML standard for providing contextual information bubbles. The Paciello Group blog pulls no punches in describing the title attribute's contribution to web interfaces:

"If you want to hide content from mobile and tablet users as well as assistive tech users and keyboard only users, use the title attribute." — The Paciello Group blog

That's pretty bad in terms of inclusion. In fact, the only place I can think of where the title attribute works reliably in screen reader software is on form elements like <input>s. Even then, touch and keyboard users won't get to see the title message appear. In short: just provide a clearly worded, permanently visible label.

An input with a big, clear, permanent label is marked good. An input with a tiny title revealed on hover is not.

I'm a big supporter of using standard HTML elements and attributes wherever they're available. It's the most efficient and performant way to build usable web interfaces. But, despite being a specification mainstay, the title attribute really isn't fit for purpose.

Then again, we've yet to define that purpose. What should tooltips be used for? And even if we can design them to be usable by the many, do we need them at all?

A case for tooltips

As we already established, tooltips are for clarification; they are for providing missing information. But why should that information be missing in the first place? As I wrote in Inclusive Design Patterns, icons can accelerate the understanding of an interface, and help to internationalize it. But icons provided in isolation are always in danger of completely confounding a user — because they don't spell anything out. To users who don't recognize or can't decipher the icon, information is missing.

Most of the time, you should simply be providing text alongside icons. Like the perma-visible field labels I just mentioned, textual labels are the most straightforward way of labeling things and they're automatically screen reader accessible if provided as real text (not images of text).

The usual excuse for not providing textual labels is, "there's no space". And there likely isn't, if you set out not to include textual labels in the first place. If you treat them as important from the beginning, you will find a way.

Left example has four labels next to one another on the same line, meaning the text has to be small. Right example puts them 2 by 2, leaving more space for bigger text.
There's always room for text if you make it, but some configurations leave more space for text than others.

Tooltips are a last resort, where space really is at a premium — perhaps due to the sheer number of controls, like in the toolbar for a WYSIWYG editor. So, how would we go about designing them to be as inclusive as possible?

Inclusive tooltips

The first thing to get right is making the text in the tooltip accessible to assistive technologies. There are a couple of different methods for associating the tooltip to the focused control, and we choose between them based on the specific role of that tooltip: Is the tooltip there to provide a primary label or an auxiliary clarification?

A notifications control with a tooltip reading "Notifications" treats the tooltip as a primary label. Alternatively, a tooltip that reads "View notifications and manage settings" is supplementary.

The left example says notifications and has the caption primary label. The right example has the longer

Tooltip as primary label

To associate one element with another as its primary label, you can use aria-labelledby. The relationship is forged by the aria-labelledby and id attributes sharing the same value.

<button class="notifications" aria-labelledby="notifications-label">  
  <svg><use xlink:href="#notifications-icon"></use></svg>
</button>  
<div role="tooltip" id="notifications-label">Notifications</div>  

To a screen reader — and its user — the above is now functionally similar to using a simple text label, like this:

<button class="notifications">Notifications</button>  

The tooltip text is available on focus, just as it would be on hover for sighted users. In fact, if all text appeared only on hover, a sighted mouse user's experience of an interface would be somewhat analogous to that of a blind screen reader user.

Redundant tooltips

All the time as an interface design consultant, I see people providing title attributes to links with identical text nodes.

<a href="/some/path" title="Heydon's special page">Heydon's special page</a>

Since the text node is already perfectly visible, this is completely redundant. It doesn't even add anything for screen readers except — in some cases — repetition.

Including notification count

What if the notifications button showed a count of unread notifications, as these things often do (I'm thinking, of course, of Twitter)?

Fortunately, aria-labelledby can accept multiple, space separated ids.

<button class="notifications" aria-labelledby="notifications-count notifications-label">  
  <svg><use xlink:href="#notifications-icon"></use></svg>
  <span id="notifications-count">3</span>
</button>  
<div role="tooltip" id="notifications-label">Notifications</div>  

Despite the #notifications-count element appearing inside the <button>, it does not form the label by itself: It forms the first part of the label as the first id listed in the aria-labelledby value. It is placed where it is so that the designer can take advantage of relative and absolute positioning to arrange the element visually.

To screen reader users, the label is now "3 notifications". This succinctly acts as both a current count of notifications and a reminder that this is the notifications control.

Tooltip as auxiliary description

Now let's try setting up the tooltip as a supplementary description, which is the archetypal form. Like <input> placeholders, tooltips should be for added information and clarification.

Some interactive elements may have accessible descriptions, but all interactive elements need accessible labels. If we're using aria-describedby to connect the tooltip text, we'll need another method for providing the "Notifications" label. Instead of aria-labelledby we can add a visually hidden span to the button's text node, alongside the existing "3" counter.

<button class="notifications" aria-describedby="notifications-count notifications-label">  
  <svg><use xlink:href="#notifications-icon"></use></svg>
  <span id="notifications-count">3</span> 
  <span class="visually-hidden">Notifications</span>
</button>  
<div role="tooltip" id="notifications-label">View and manage notifications settings</div>  

The visually-hidden class corresponds to some special CSS we've discussed before on Inclusive Components. It hides the <span> visually without stopping it from being read out in screen readers.

.visually-hidden {
  clip-path: inset(100%);
  clip: rect(1px, 1px, 1px, 1px);
  height: 1px;
  overflow: hidden;
  position: absolute;
  white-space: nowrap;
  width: 1px;
}

The prescribed behavior of aria-describedby is to be announced as the last piece of information for the control, after the label and role. In this case: "Notifications button… View and manage notifications settings" (most screen readers will leave a pause before the description).

Interaction

To improve upon the notoriously awful title attribute, our custom tooltips should appear on focus as well as hover. By supplying the tooltip in an element adjacent to the button, we can do this with just CSS:

[role="tooltip"] {
  display: none;
}

button:hover + [role="tooltip"],  
button:focus + [role="tooltip"] {  
  display: block;
}

However, we may need to wrap the button and tooltip in a container element for positioning purposes:

.button-and-tooltip {
  position: relative;
}

[role="tooltip"] {
  position: absolute;
  /* left/top/right/bottom values as required */
}

Touch interaction

So far this simply doesn't work so well for touch screen users because the focus and active states happen simultaneously. In practice, this means you'll see the tooltip, but only as the button is being pressed.

How much of a problem this is depends on the nature of the app to which the control belongs. How bad is it if the user presses the control without really knowing what it does the first time? How easily can they recover?

There are other things you could try, of course. One might be to suppress the button's action on the first press so it just shows the tooltip that time around. You could take this "tutorial mode" idea further still and show the tooltips as inline text for newcomers, but streamline the interface to just show icons for established users. By then, they should have learned what the icons represent.

Left example for a new user has icons with text labels. Right example for an established user has just icons
In either case, the landing screen for each of the options should have a clear (<h1>) heading with the same wording as the labels. Then at least the user knows where the icon took them upon arrival.

This would be to do away with tooltips altogether, which is probably for the best anyway. However, the tooltip's sister component — the toggletip — can work for mouse, keyboard and touch users.

Inclusive toggletips

Toogletips are like tooltips in the sense that they can provide supplementary or clarifying information. However, they differ by making the control itself supplementary: toggletips exist to reveal information balloons, and serve no other purpose.

Often they take the form of little "i" icons:

A round

To work by touch just as well as by mouse or keyboard, toggletips are revealed by click rather than by hover and focus. Crucially, this means the aria-describedby association is no longer appropriate. Why? Because a screen reader user would have access to the information before pressing the button, so pressing it would appear not to do anything. Technically, they have access to the information, making the control "accessible" — but the control simply wouldn't make sense. In other words, it's a user experience issue more than a strict accessibility one, but it's important.

Toggletips with live regions

The trick is to make screen readers announce the information after the click event. This is a perfect use case for a live region. We can supply an empty live region, and populate it with the toggletip "bubble" when it is invoked. This will both make the bubble appear visually and cause the live region to announce the tooltip's information.

To follow is the markup with the live region unpopulated. Note the .tooltip-container element, which is provided to help positioning. This element would have position: relative, allowing for absolutely positioning the generated .toggletip-bubble element nearby.

<span class="tooltip-container">  
  <button type="button" aria-label="more info" data-toggletip-content="This clarifies whatever needs clarifying">i</button>
  <span role="status"></span>
</span>  

Note the type="button" attribution which is to stop some browsers mistaking the button as a submit button when placed inside forms. Here is the markup with the live region populated (after the toggletip button is clicked):

<span class="tooltip-container">  
  <button type="button" aria-label="more info" data-toggletip-content="This clarifies whatever needs clarifying">i</button>
  <span role="status">
    <span class="toggletip-bubble">This clarifies whatever needs clarifying</span>
  </span>
</span>  

The accompanying script and codePen, with notes to follow:

(function() {
  // Get all the toggletip buttons
  var toggletips = document.querySelectorAll('[data-toggletip-content]');

  // Iterate over them
  Array.prototype.forEach.call(toggletips, function (toggletip) {
    // Get the message from the data-content element
    var message = toggletip.getAttribute('data-toggletip-content');

    // Get the live region element
    var liveRegion = toggletip.nextElementSibling;

    // Toggle the message
    toggletip.addEventListener('click', function () {
        liveRegion.innerHTML = '';
        window.setTimeout(function() {
          liveRegion.innerHTML = '<span class="toggletip-bubble">'+ message +'</span>';
        }, 100);
    });

    // Close on outside click
    document.addEventListener('click', function (e) {
      if (toggletip !== e.target) {
        liveRegion.innerHTML = '';
      }                        
    });

    // Remove toggletip on ESC
    toggletip.addEventListener('keydown', function(e) {
      if ((e.keyCode || e.which) === 27)
      liveRegion.innerHTML = '';
    });
  });
}());

Notes

Progressively enhancing title

As discussed, the title attribute is really flaky. But it does at least provide an accessible label to some assistive technologies, available when the button is focused. We could provide the bubble content via title and use this to build the data-toggletip-content attribute on page load. Our script's initial hook now becomes the boolean data-toggletip:

<button data-toggletip aria-label="more info" title="This clarifies whatever needs clarifying">i</button>  

In the script, we need to take the value from title to build data-tooltip-content, then destroy title because we don't need it and it might still appear / get announced if left festering.

var toggletips = document.querySelectorAll('[data-toggletip][title]');

Array.prototype.forEach(toggletips, function (toggletip) {  
  var message = toggletip.getAttribute('title');
  toggletip.setAttribute('data-tooltip-content', message);
  toggletip.removeAttribute('title');
});

Better progressive enhancement

A button that doesn't do anything and happens to have a title attribute isn't really a very good baseline. Instead, I would recommend displaying the toggletip's content inline and then enhancing by creating the toggletip button dynamically.

Here's another codePen, which progressively enhances a simple paragraph into a toggletip:

Tests and error messages

Something I haven't talked about on Inclusive Components is writing tests, so let's do a little of that here. Don't worry, I don't mean unit tests.

If our toggletip component is to belong to a design system, it may be borrowed and used by lots of different people. By writing tests and including warnings, we can try to ensure it isn't being used improperly.

A toggletip button that isn't a <button> provides a deceptive role to assistive technologies and is not focusable by keyboard (unless it's another, inappropriate focusable element like a hyperlink). In our script, we can detect the element nodeName and return an error message if it is not BUTTON. We use return to stop the remainder of the IIFE (Immediately Invoked Function Expression) from executing.

if (toggletip.nodeName !== 'BUTTON') {  
  console.error('Toggletip buttons need to be <button> elements.')
  return;
}

The error message screenshot form developer tools

CSS tests and error messages

In Inclusive Design Patterns I write about creating deliberate visual regressions to highlight code errors, and providing error messages in the developer tools CSS inspector.

The error we caught with JavaScript earlier can be caught using the CSS selector [data-tooltip]:not(button). We can highlight the erroneous element with a red outline, and provide an error message using the made-up ERROR property:

[data-tooltip]:not(button) {
  outline: red solid 0.5em;
  ERROR: Toggletip buttons need to be <button> elements.
}

Despite being an invalid property, the ERROR will appear in dev tools when the element is inspected.

The clear red outline shows there is an error present and guides the developer's DOM inspector cursor.

Conclusion

Most of the time, tooltips shouldn't be needed if you provide clear textual labeling and familiar iconography. Most of the time toggletips are a complex way of providing information that could just be part of the document's prose content. But I see each of these components all the time in auditing websites, so I wanted to provide some guidance on how to make the most of them.

Checklist

Back to components list