Pure CSS Tooltips Using attr() and ::after
Pure CSS tooltips allow developers to display contextual hints
without relying on JavaScript libraries or extra DOM elements. By
combining custom HTML data-* attributes with the CSS
attr() function and the ::after
pseudo-element, you can dynamically extract text from your markup and
render it as a styled tooltip when a user hovers over or focuses on an
element. This technique results in clean, performant, and maintainable
code that minimizes DOM bloat.
The HTML Structure
The foundation of this technique relies on a custom HTML data
attribute, commonly named data-tooltip. You can attach this
attribute to any standard HTML element, such as buttons, links, or
spans.
<button class="tooltip-trigger" data-tooltip="This is the tooltip text">
Hover over me
</button>The attribute value contains the exact string you want to display inside the tooltip.
How the CSS Works
The implementation relies on three main concepts: relative
positioning on the parent, dynamic content generation via
attr(), and visibility toggling on interaction.
1. Preparing the Parent Container
The parent element must establish a positioning context so that the pseudo-element can be positioned accurately relative to it.
.tooltip-trigger {
position: relative;
display: inline-block;
cursor: pointer;
}2. Generating the
Content with ::after and attr()
The ::after pseudo-element creates an inline-level box
inside the element, after its actual content. The CSS
content property accepts the attr() functional
notation, which retrieves the string stored in the specified HTML
attribute.
.tooltip-trigger::after {
content: attr(data-tooltip);
position: absolute;
bottom: 125%;
left: 50%;
transform: translateX(-50%);
padding: 6px 10px;
border-radius: 4px;
background-color: #333333;
color: #ffffff;
font-size: 0.875rem;
white-space: nowrap;
pointer-events: none;
opacity: 0;
visibility: hidden;
transition: opacity 0.2s ease, transform 0.2s ease;
z-index: 10;
}Key properties used above:
content: attr(data-tooltip);: Reads the value ofdata-tooltipdirectly from the HTML element and injects it into the pseudo-element.pointer-events: none;: Prevents the tooltip from interfering with mouse interactions or flickering when hovered over.opacity: 0;andvisibility: hidden;: Keeps the tooltip completely hidden from screen rendering and accessibility flows until triggered.
3. Triggering the Display
To reveal the tooltip, define transition states for both hover and keyboard focus states:
.tooltip-trigger:hover::after,
.tooltip-trigger:focus-visible::after {
opacity: 1;
visibility: visible;
transform: translateX(-50%) translateY(-4px);
}Using :focus-visible alongside :hover
ensures that keyboard users navigating via the Tab key can
view the tooltip content just like mouse users.
Optional: Adding a Tooltip Arrow
You can pair the ::after element (the tooltip bubble)
with the ::before pseudo-element to create a small pointer
arrow underneath the box:
.tooltip-trigger::before {
content: '';
position: absolute;
bottom: 115%;
left: 50%;
transform: translateX(-50%);
border-width: 5px;
border-style: solid;
border-color: #333333 transparent transparent transparent;
opacity: 0;
visibility: hidden;
transition: opacity 0.2s ease;
}
.tooltip-trigger:hover::before,
.tooltip-trigger:focus-visible::before {
opacity: 1;
visibility: visible;
}Limitations and Considerations
While attr() is efficient for short textual
descriptions, it currently supports only string values in stable CSS
implementations and cannot interpret HTML markup, links, or images
inside the tooltip. For supplementary screen reader support, ensure
vital informational text is paired with appropriate
aria-label or aria-describedby attributes.