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:
- Desktop vs Mobile: The component needed to work differently based on the device - hover for desktop, touch for mobile
- Keyboard Accessibility: Users should be able to access and dismiss tooltips using only a keyboard
- Screen Reader Support: The content should be announced appropriately to assistive technology users
- 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!