Skip to content

Creating an accessible tooltip

Part 3 of Designing accessible components using the ustwo Inclusivity Principles

Alasdair BlackwellยทFull-stack Tech Lead

Published on

Overview

We recently worked on a large project for an energy company that required accessible React components; we've all got to pay our bills, so it was critical the service worked for everyone.

The humble tooltip

Tooltips offer designers a way of keeping interfaces clean whilst still providing users with the information they need.

So how do you make them accessible?

The second of the ustwo Inclusivity Principles tells us to "Enjoy the patterns" โ€“ so we should go and look for an existing design pattern, and follow that.

The W3C Tooltip Pattern is a work in progress: "it does not yet have task force consensus". Hmm. Ah! The Github discussion for the W3C reference implementation has a highly up-voted comment recommending Heydon Pickering's Toggletip pattern instead.

I have Heydon's book, Inclusive Components on my shelf. But our designer would like the tooltip to reveal content on hover on desktop, and on tap on mobile.

Heydon's toggletip pattern will get us three-quarters of the way there. We will need to adapt it to support hover and tap. We hope that this blogpost will serve as permanent documentation for an accessible hover/tap toggletip pattern.

(Which, by the way, is very satisfying to say.)

An accessible hover/tap toggletip pattern

I've always believed in showing, rather than telling:

This is an example of our accessible hover/tap toggletip component.

The Requirements

When implementing this tooltip component, we faced several key requirements:

  1. Desktop vs Mobile: The component needed to work differently based on the device - hover for desktop, touch for mobile
  2. Keyboard Accessibility: Users should be able to access and dismiss tooltips using only a keyboard
  3. Screen Reader Support: The content should be announced appropriately to assistive technology users
  4. Visual Flexibility: Support for different positions (up, down, left, right) and color schemes

Implementation Details

Our solution draws inspiration from Heydon Pickering's toggletip pattern while adapting it for our specific needs. Here's how we tackled each requirement:

Device-Specific Interaction

const isMobile = typeof window !== 'undefined' && window.innerWidth < 768;

// In the button element:
onMouseOver={() => !isMobile && setIsToggled(true)}
onMouseOut={() => !isMobile && setIsToggled(false)}
onClick={() => setIsToggled(!isToggled)}

On desktop, tooltips appear on hover. On mobile devices, they toggle on tap. We naively detect the device type using a simple width check and adjust the behavior accordingly.

This is fine and a great example of progressive enhancement, since it will default to toggletip (i.e. tap) behaviour.

Keyboard Support

useEffect(() => {
  const handleEscape = (e: KeyboardEvent) => {
    if (e.key === "Escape") {
      setIsToggled(false);
    }
  };

  if (a11yVisible) {
    document.addEventListener("keydown", handleEscape);
  }
  return () => {
    document.removeEventListener("keydown", handleEscape);
  };
}, [a11yVisible]);

Users can:

  • Tab to the tooltip trigger
  • Press Enter/Space to show the tooltip
  • Press Escape to dismiss it
  • Tab away to automatically hide it

Screen Reader Announcements

Instead of using ARIA's tooltip role (which lacks broad support), we opted for a live region via role="status" on the element containing the tooltip content. This ensures that tooltip content is announced when it appears:

<span role="status">
  {a11yVisible && (
    <>
      <span className={isToggled ? "" : styles.hidden}>
        <span className={getArrowClassName()}></span>
      </span>
      <span className={getTooltipBoxClassName()}>{message}</span>
    </>
  )}
</span>

The button only contains an icon, so we need to give it an appropriate aria-label:

<button
  aria-label="More info"
  className={styles.button}
  // ... other props
>

Layout Flexibility

We support multiple tooltip positions through an enum:

export enum TooltipLayout {
  Up = "up",
  Down = "down",
  Right = "right",
  Left = "left",
  UpCenter = "upCenter",
}

This allows for different positioning based on screen size and context:

const [tooltipPosition, setTooltipPosition] = useState(layoutDesktop);

useEffect(() => {
  if (isToggled) {
    setTooltipPosition(isMobile ? layoutMobile : layoutDesktop);
  }
}, [isToggled, isMobile, layoutMobile, layoutDesktop]);

Usage

Using the tooltip component is straightforward:

<Tooltip
  message="Your tooltip message"
  layoutDesktop={TooltipLayout.Right}
  layoutMobile={TooltipLayout.Up}
>
  Trigger element
</Tooltip>

Conclusion

While there's no official W3C pattern for tooltips yet, we created a solution that:

  • Works seamlessly across devices
  • Maintains accessibility
  • Provides flexibility for different use cases
  • Follows progressive enhancement principles

We're particularly proud of how it maintains a balance between modern UX expectations and inclusive design principles.

We hope this blog post serves as a useful reference for anyone looking to implement an accessible tooltip component. If you have any questions or suggestions, feel free to get in touch!

At ustwo, we follow our own inclusive design principles to ensure everything we build is accessible to everyone. If you'd like to have a chat about working with ustwo or joining our team, head over to ustwo.com

About the author:

Alasdair Blackwell headshot

Alasdair Blackwell

Full-stack Tech Lead - London, UK

Alasdair is passionate about privacy, accessibility, and user-centred design. He is at his happiest implementing a user journey in a way that works for everyone.