- Published on
The Ultimate Guide to Pure CSS Tooltips: No JavaScript Required
- Authors
- Name
- Md Nasim Sheikh
- @nasimStg
'The Ultimate Guide to Pure CSS Tooltips: No JavaScript Required'
Learn how to create beautiful, responsive, and performant tooltips using only HTML and CSS. This comprehensive guide covers everything from basic implementation to advanced positioning, styling, and accessibility.
Table of Contents
- 'The Ultimate Guide to Pure CSS Tooltips: No JavaScript Required'
- Why Bother with Pure CSS Tooltips?
- Section 1: The Fundamental Building Blocks
- The HTML Structure
- The Core CSS Concepts
- Section 2: Mastering Tooltip Positioning
- Positioning on Top
- Positioning on the Bottom, Left, and Right
- Section 3: Adding the Pointy Arrow (Caret)
- Section 4: A Cleaner Approach with data-* Attributes
- The HTML
- The CSS
- Section 5: The Unskippable Topic - Accessibility (ARIA)
- Making it Keyboard Accessible
- Making it Screen Reader Accessible with ARIA
- Section 6: Best Practices and Final Polish
- Conclusion
Tooltips are one of the most common UI patterns on the web. They provide a fantastic way to offer contextual information, hints, or clarifications without cluttering the interface. When faced with implementing one, many developers instinctively reach for a JavaScript library like Tippy.js or Popper.js. And while those libraries are incredibly powerful, they're often overkill for simple, informational tooltips.
What if I told you that you can create robust, stylish, and performant tooltips using only the tools you already know and love: HTML and CSS? That's right—no JavaScript, no dependencies, no extra weight on your page load.
In this comprehensive guide, we'll dive deep into the world of pure CSS tooltips. We'll start with the absolute basics and progressively build up to advanced techniques, including custom positioning, animated arrows, and crucial accessibility considerations. Let's get started!
Why Bother with Pure CSS Tooltips?
Before we jump into the code, let's establish why this is a worthwhile skill to have in your developer toolkit. In a world dominated by JavaScript frameworks, it's easy to forget the power of CSS.
- Blazing Fast Performance: The biggest win is performance. There's no JavaScript to download, parse, and execute. The browser's rendering engine handles everything natively. This means a lighter page and a snappier user experience, especially on mobile devices or slower connections.
- Simplicity and Zero Dependencies: Your tooltip's logic lives right alongside your other styles. There's no need to manage an external library, worry about version conflicts, or add another
<script>
tag to your project. It's just clean, lean HTML and CSS. - Enhanced Maintainability: Keeping your styling logic within your CSS files makes your codebase more organized and easier to maintain. You won't have to hunt down a separate JS file to tweak the appearance of a tooltip.
- Great for Static Sites & CSS-in-JS: This approach is perfect for static site generators (like Jekyll, Hugo) or environments where you want to minimize JavaScript. It also plays beautifully with CSS-in-JS libraries (like Styled Components or Emotion), as the logic translates directly.
Section 1: The Fundamental Building Blocks
The magic behind a pure CSS tooltip lies in a simple concept: we have an element that is initially hidden and becomes visible when a user interacts with its parent element (usually via hovering or focusing).
Let's break down the core components.
The HTML Structure
At its heart, a tooltip requires two parts: the trigger (the element you interact with) and the tooltip content (the text that pops up). The most straightforward way to structure this is by nesting the tooltip content inside a container element.
<div class="tooltip-container">
Here is the trigger text.
<span class="tooltip-text">And this is the tooltip content!</span>
</div>
Here's why this structure is so effective:
tooltip-container
: Thisdiv
acts as our positioning context. We'll give itposition: relative;
so we can position the tooltip absolutely within it.tooltip-text
: Thisspan
holds our tooltip's message. It's an inline element by default, but we'll change its properties with CSS to make it behave like a popup.
The Core CSS Concepts
Now, let's translate that structure into a visual reality with CSS. The logic revolves around a few key properties:
position
: As mentioned, we set the container toposition: relative;
and the tooltip itself toposition: absolute;
. This is the cornerstone of our positioning strategy.visibility
andopacity
: To hide the tooltip initially, we'll usevisibility: hidden;
andopacity: 0;
. Why both?visibility: hidden;
removes the element from the accessibility tree and prevents any interaction with it.opacity: 0;
allows us to create a smooth fade-in animation usingtransition
.- The
:hover
Pseudo-class: This is our trigger. When the user hovers over.tooltip-container
, we'll target the child.tooltip-text
and change itsvisibility
andopacity
to make it appear. transition
: To avoid a jarring, instant appearance, we'll add atransition
to theopacity
property for a professional-looking fade effect.
Let's put it all together in a basic example.
/* The container needs to be relative to position the tooltip within it */
.tooltip-container {
position: relative;
display: inline-block; /* Or 'block', depending on your layout */
cursor: help; /* A helpful cursor to indicate more info is available */
border-bottom: 1px dotted black; /* A visual cue */
}
/* The tooltip text - hidden by default */
.tooltip-text {
visibility: hidden;
opacity: 0;
transition: opacity 0.3s ease;
/* Basic styling */
width: 160px;
background-color: #333;
color: #fff;
text-align: center;
padding: 8px 10px;
border-radius: 6px;
/* Absolute positioning */
position: absolute;
z-index: 1; /* Ensure it's on top of other content */
}
/* Show the tooltip on hover */
.tooltip-container:hover .tooltip-text {
visibility: visible;
opacity: 1;
}
With this HTML and CSS, you already have a functional, albeit unpositioned, tooltip!
Section 2: Mastering Tooltip Positioning
A tooltip that just appears somewhere randomly isn't very useful. The next step is to control where it appears relative to the trigger element. We can create modifier classes to handle top, bottom, left, and right positioning.
Our base .tooltip-text
style will contain all the common properties (color, background, etc.), and the modifier classes will handle the unique positioning.
Positioning on Top
To place the tooltip above the trigger, we need to push it up from the bottom of the container and then center it horizontally.
/* For a tooltip that appears on top */
.tooltip-top {
bottom: 125%; /* Move it up above the trigger text */
left: 50%; /* Center it horizontally relative to the container */
transform: translateX(-50%); /* Nudge it back by half its own width to truly center */
}
How it works:
bottom: 125%
: This positions the bottom of the tooltip 125% of the container's height away from the bottom of the container. The extra 25% creates a small gap.left: 50%
: This aligns the left edge of the tooltip with the horizontal center of the container.transform: translateX(-50%)
: This is the key to perfect centering. It shifts the tooltip to the left by 50% of its own width, aligning its center with the container's center.
Positioning on the Bottom, Left, and Right
We can apply the same logic for the other directions.
/* For a tooltip that appears on the bottom */
.tooltip-bottom {
top: 125%;
left: 50%;
transform: translateX(-50%);
}
/* For a tooltip that appears on the right */
.tooltip-right {
left: 105%; /* A little space from the right edge of the trigger */
top: 50%;
transform: translateY(-50%); /* Center vertically */
}
/* For a tooltip that appears on the left */
.tooltip-left {
right: 105%; /* A little space from the left edge of the trigger */
top: 50%;
transform: translateY(-50%); /* Center vertically */
}
Your HTML would then look like this:
<div class="tooltip-container">
Top Tooltip
<span class="tooltip-text tooltip-top">I appear on top!</span>
</div>
<div class="tooltip-container">
Right Tooltip
<span class="tooltip-text tooltip-right">I appear on the right!</span>
</div>
Section 3: Adding the Pointy Arrow (Caret)
What's a classic tooltip without that little pointy arrow indicating its source? We can create this purely with CSS using a clever border trick on a pseudo-element (::after
).
The trick involves creating a zero-width, zero-height element and giving it thick borders. By making three of the borders transparent, the one remaining border forms a triangle.
Let's add an arrow to our top tooltip.
/* Add this to your existing .tooltip-text class */
.tooltip-text::after {
content: "";
position: absolute;
border-width: 5px;
border-style: solid;
}
/* Position the arrow for the TOP tooltip */
.tooltip-top::after {
top: 100%; /* Position it at the bottom of the tooltip */
left: 50%;
transform: translateX(-50%);
border-color: #333 transparent transparent transparent;
}
How it works:
content: ""
: Required for any pseudo-element to be generated.position: absolute
: We position the arrow relative to its parent, the.tooltip-text
span.top: 100%
: This places the top of the pseudo-element right at the bottom edge of the tooltip box.border-color: #333 transparent transparent transparent
: This is the magic. The top border gets the color of our tooltip's background, while the others are transparent. This creates a downward-pointing triangle.
Here are the arrow styles for the other positions:
/* Arrow for the BOTTOM tooltip */
.tooltip-bottom::after {
bottom: 100%;
left: 50%;
transform: translateX(-50%);
border-color: transparent transparent #333 transparent;
}
/* Arrow for the RIGHT tooltip */
.tooltip-right::after {
right: 100%;
top: 50%;
transform: translateY(-50%);
border-color: transparent #333 transparent transparent;
}
/* Arrow for the LEFT tooltip */
.tooltip-left::after {
left: 100%;
top: 50%;
transform: translateY(-50%);
border-color: transparent transparent transparent #333;
}
data-*
Attributes
Section 4: A Cleaner Approach with While the <span>
method is robust, it adds extra markup to your HTML. For very simple tooltips that only contain text, we can achieve an even cleaner result using data-*
attributes and pseudo-elements for everything.
The HTML
The HTML becomes incredibly lean:
<button class="tooltip" data-tooltip="I'm a tooltip from a data attribute!">Hover Me</button>
The CSS
We'll use the ::before
pseudo-element for the tooltip box and ::after
for the arrow. The tooltip text is pulled directly from the HTML using attr(data-tooltip)
.
.tooltip {
position: relative;
cursor: help;
}
/* Tooltip Box */
.tooltip::before {
content: attr(data-tooltip); /* Get the text from the data-tooltip attribute */
position: absolute;
/* Position it on top */
bottom: 125%;
left: 50%;
transform: translateX(-50%);
/* Hide it by default */
opacity: 0;
visibility: hidden;
transition: opacity 0.3s ease;
/* Styling */
background-color: #333;
color: #fff;
padding: 8px 10px;
border-radius: 6px;
white-space: nowrap; /* Prevent the tooltip from breaking into multiple lines */
z-index: 1;
}
/* Tooltip Arrow */
.tooltip::after {
content: "";
position: absolute;
/* Position it on top */
bottom: 110%; /* Adjust to connect with the box */
left: 50%;
transform: translateX(-50%);
/* Hide it by default */
opacity: 0;
visibility: hidden;
transition: opacity 0.3s ease;
/* The triangle */
border-width: 5px;
border-style: solid;
border-color: #333 transparent transparent transparent;
}
/* Show on hover */
.tooltip:hover::before,
.tooltip:hover::after {
opacity: 1;
visibility: visible;
}
Pros:
- Extremely clean HTML.
- Self-contained component logic.
Cons:
- You can't put HTML (like links or bold text) inside a
data-*
attribute. - It's much harder to make this approach fully accessible, which brings us to our next crucial section.
Section 5: The Unskippable Topic - Accessibility (ARIA)
A tooltip that can't be used by everyone isn't a good tooltip. Pure CSS tooltips have two major accessibility hurdles:
- Keyboard Focus: The
:hover
pseudo-class only works for mouse users. Keyboard navigators use theTab
key, which triggers the:focus
state. - Screen Readers: A screen reader might not know that the new text that just appeared is related to the element the user is focused on.
We can solve both of these issues with careful implementation.
Making it Keyboard Accessible
First, ensure your trigger element is focusable. Elements like <a>
, <button>
, and <input>
are focusable by default. If you're using a <span>
or <div>
as a trigger, you must add tabindex="0"
to it.
Next, simply add the :focus
pseudo-class alongside :hover
in your CSS selectors.
/* OLD */
.tooltip-container:hover .tooltip-text { ... }
/* NEW & IMPROVED */
.tooltip-container:hover .tooltip-text,
.tooltip-container:focus .tooltip-text {
visibility: visible;
opacity: 1;
}
Making it Screen Reader Accessible with ARIA
This is where the <span>
method shines over the data-*
attribute method. To create a proper semantic link between the trigger and the tooltip, we use the aria-describedby
attribute.
Here's the gold-standard, accessible HTML structure:
<button class="tooltip-trigger" aria-describedby="tooltip-1">
Learn More
</button>
<span class="tooltip-text tooltip-top" role="tooltip" id="tooltip-1">
This is an accessible tooltip!
</span>
What's new?
id="tooltip-1"
: The tooltip content needs a unique ID.aria-describedby="tooltip-1"
: The trigger element uses this attribute to point to the ID of its tooltip. This tells screen readers, "The element with ID 'tooltip-1' provides a description for me."role="tooltip"
: This explicitly tells assistive technologies that this<span>
is, in fact, a tooltip.
Important Note: This structure requires the tooltip <span>
to be a sibling of the trigger, not a child. This means our CSS needs a slight adjustment using the adjacent sibling selector (+
).
/* Accessible CSS */
.tooltip-trigger:hover + .tooltip-text,
.tooltip-trigger:focus + .tooltip-text {
visibility: visible;
opacity: 1;
}
This is the most robust and responsible way to build a tooltip, ensuring it works for all users.
Section 6: Best Practices and Final Polish
Let's add a few final touches to make our tooltips truly production-ready.
Add a Delay: A tooltip that appears instantly can be annoying if a user is just moving their mouse across the screen. Use
transition-delay
to make it appear only after a brief, intentional hover..tooltip-text { /* ... other styles */ transition: opacity 0.3s ease; transition-delay: 0.4s; /* Add a delay on show */ } .tooltip-container:hover .tooltip-text { /* ... other styles */ transition-delay: 0s; /* Remove delay on hide */ }
Handle Multiline Text: By default, our tooltips might overflow if the text is long. Set a
max-width
and allow text to wrap..tooltip-text { /* ... other styles */ width: auto; /* Let it size to its content */ max-width: 250px; /* But not too wide */ white-space: normal; /* Allow text to wrap */ }
Know the Limitations: Pure CSS tooltips are amazing, but they can't solve everything. If a tooltip is near the edge of the browser window, it can get cut off. Dynamically "flipping" the tooltip's position to keep it in the viewport requires JavaScript to measure element and window dimensions. For these complex scenarios, a JS library is the right tool for the job.
Conclusion
You've now journeyed from the basic concept to a fully-featured, accessible, and polished CSS-only tooltip. You've learned how to position it, style it with a caret, and implement it using different markup strategies.
While JavaScript libraries certainly have their place for complex, dynamic UIs, don't underestimate the power and elegance of CSS. For a vast number of use cases, a pure CSS tooltip is not only sufficient but superior—offering better performance, simpler maintenance, and zero dependencies.
So next time you need to add a little contextual help to your interface, give this technique a try. You might be surprised at just how far you can get with a bit of clever CSS.