Tooltip Components Should Not Exist

Design Systems

Standalone tooltip components in design systems often lead to accessibility and UX issues. This article advocates for higher-level, pattern-specific solutions for consistent, inclusive web experiences.

Photo by Daniel Herron

#1: Designing Design Systems

#2: Tooltip Components Should Not Exist

I must admit, poorly implemented tooltips in web applications are one of my biggest pet peeves. To be fair, getting tooltips right is surprisingly difficult. Numerous critical considerations must be addressed:

  • Accessibility
  • Keyboard interactivity
  • Ensuring the least surprise for all users
  • Avoiding the hiding of critical information

Over the years, I've observed many design systems attempting to implement a <Tooltip> component for developers to use. In my opinion, the one commonality among them is that they are not used as intended. From an API design perspective, this likely indicates that the <Tooltip> component is an abstraction at too low a level, which leads me to today's strong assertion 🌶:

Tooltip Components Should Not Exist

Let me clarify: I am not saying tooltips themselves should be abolished. They are a valuable tool when applied "correctly" – whatever that may entail for your specific use cases. While education might help, let's be honest: very few people diligently read documentation, and AI tools often reproduce existing patterns, potentially amplifying anti-patterns already present in our codebase.

Therefore, before outlining what an alternative approach could look like, let's delve into the potential problems that arise from misusing tooltips.

Keyboard Interactivity

This point should be self-evident: we are obligated to build web applications for everyone, which means acknowledging that not all users will operate with a mouse. Despite this, I frequently encounter implementations where tooltips only appear on hover, especially when the triggering element is non-interactive.

We need look no further than Material UI, one of the most popular component libraries. Their basic tooltip example from the documentation demonstrates this:

<Tooltip title="Delete">
  <IconButton>
    <DeleteIcon />
  </IconButton>
</Tooltip>

This is straightforward and functional. However, what happens if we simply wrap an Icon or another non-interactive element, like a Badge, with a <Tooltip>?

<Tooltip title="Home">
  <IconHome />
</Tooltip>

<Tooltip title="Unread Mails">
  <Badge badgeContent={4} color="primary">
    <IconMail color="action" />
  </Badge>
</Tooltip>

Precisely – the tooltip will still appear on hover, but not on focus, because tabbing will simply bypass the element. Since the element isn't keyboard-interactive, it never receives focus, meaning the tooltip will never be displayed when navigating solely with the keyboard.

Furthermore, there's no way to enforce type safety here, as we cannot control the type of children passed to the <Tooltip> component.

React Aria's approach offers a slight improvement: it won't display the tooltip at all for non-interactive elements, making the issue easier to spot for developers who primarily use their mouse. The proposed fix involves wrapping the custom trigger with a <Focusable> component. While this is significantly better than other methods, it still suffers from other drawbacks, specifically:

Least Surprise For All Users

If a <Tooltip> can be added to any element, someone inevitably will. I've encountered tooltips in the most unexpected places, such as on a specific word in the middle of a sentence – without any visual cue that additional information is available.

Conversely, I've also observed the opposite problem. It's incredibly frustrating to see a button consisting solely of an incomprehensible icon, with no accompanying information explaining its function upon click.

Such inconsistencies are practically guaranteed to emerge in your application once a critical mass of developers and designers begin contributing.

So, if a standalone <Tooltip> component isn't ideal, what alternative approaches can a design system employ to expose tooltip behavior? My stance is:

Provide only higher-level pattern components that enforce consistent and accessible tooltip usage.

Here are some strategies that have proven highly effective in the past:

  • Interactive components like <Button> or <Link> should accept an optional title prop. This allows for displaying additional information when desired. Interactive elements are typically easily discoverable in the UI and accessible via keyboard tabbing.
  • <IconButton> should have a required title prop. This ensures that icon functions are explained to users and provides a proper label for accessibility.
  • Expose an additional <InfoIcon> component to render an info or question mark icon alongside a tooltip. This clearly indicates where users can find more context, and we can internally guarantee its focusability.
  • Create an <InfoText> component to provide more context to text. This component should be visually distinct from other text, perhaps with a dashed underline, and crucially, it must also be keyboard interactive.

I'm sure there are other valuable patterns I might be overlooking, but in such cases, introducing another pattern-specific component is preferable to granting universal access to a low-level <Tooltip> component. Don't misunderstand; flexibility is excellent for many scenarios, such as layout design. However, providing a consistent and inclusive user experience is paramount for tooltips, which is why a more restrictive approach is superior.

Restriction can also foster creativity. If we can't simply hide information behind a tooltip due to screen space limitations, it might encourage us to fundamentally rethink our design solutions.

Therefore, if you're building a design system for your organization, try to resist the urge to include a <Tooltip> component in your public interface.

That concludes today's discussion. Feel free to reach out to me on Bluesky if you have any questions, or simply leave a comment below. ⬇️

Like the monospace font in the code blocks? Check out monolisa.dev