Episode 3 of 8

CSS-only Tooltip

Build elegant, accessible tooltips using only CSS pseudo-elements, the attr() function, and smooth transitions.

CSS-only Tooltip

Tooltips provide extra information when a user hovers over an element. In this episode you will build fully functional tooltips using only CSS — no JavaScript, no tooltip libraries. We will use pseudo-elements, the attr() function, and CSS transitions.

The HTML — Using data Attributes

<button class="tooltip" data-tooltip="Save your progress">
    Save
</button>

<a href="#" class="tooltip" data-tooltip="Go back to the homepage">
    Home
</a>

<span class="tooltip" data-tooltip="This field is required">
    Username *
</span>

The tooltip text is stored in a data-tooltip attribute. Any element with the class .tooltip and a data-tooltip attribute will show a tooltip on hover.

The Tooltip Bubble — ::after Pseudo-element

.tooltip {
    position: relative;
    cursor: pointer;
}

.tooltip::after {
    content: attr(data-tooltip);
    position: absolute;
    bottom: calc(100% + 8px);
    left: 50%;
    transform: translateX(-50%);
    padding: 6px 12px;
    background: #333;
    color: #fff;
    font-size: 13px;
    white-space: nowrap;
    border-radius: 4px;
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.3s ease, transform 0.3s ease;
    transform: translateX(-50%) translateY(4px);
}

The content: attr(data-tooltip) pulls the tooltip text directly from the HTML attribute. The tooltip is positioned above the element using bottom: calc(100% + 8px) with an 8px gap. It starts invisible (opacity: 0) and slightly shifted down (translateY(4px)) for a slide-up entrance effect.

The Arrow — ::before Pseudo-element

.tooltip::before {
    content: "";
    position: absolute;
    bottom: calc(100% + 2px);
    left: 50%;
    transform: translateX(-50%);
    border: 6px solid transparent;
    border-top-color: #333;
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.3s ease;
}

The arrow is a CSS triangle created with the border trick. A 6px transparent border on all sides, with only the top border colored, creates a small downward-pointing triangle that connects the tooltip bubble to the element below it.

Showing the Tooltip on Hover

.tooltip:hover::after {
    opacity: 1;
    transform: translateX(-50%) translateY(0);
}

.tooltip:hover::before {
    opacity: 1;
}

On hover, both the bubble and arrow fade in. The bubble also slides up to its final position (translateY(0)), creating a smooth entrance animation.

Tooltip Positions

The default tooltip appears above. Here is how to create bottom, left, and right variants:

Bottom Tooltip

.tooltip-bottom::after {
    bottom: auto;
    top: calc(100% + 8px);
    transform: translateX(-50%) translateY(-4px);
}

.tooltip-bottom::before {
    bottom: auto;
    top: calc(100% + 2px);
    border-top-color: transparent;
    border-bottom-color: #333;
}

.tooltip-bottom:hover::after {
    transform: translateX(-50%) translateY(0);
}

Left Tooltip

.tooltip-left::after {
    bottom: auto;
    left: auto;
    right: calc(100% + 8px);
    top: 50%;
    transform: translateY(-50%) translateX(4px);
}

.tooltip-left::before {
    bottom: auto;
    left: auto;
    right: calc(100% + 2px);
    top: 50%;
    transform: translateY(-50%);
    border-top-color: transparent;
    border-right-color: transparent;
    border-left-color: #333;
}

.tooltip-left:hover::after {
    transform: translateY(-50%) translateX(0);
}

Styling Variations

/* Dark theme */
.tooltip-dark::after {
    background: #1a1a2e;
    color: #e0e0e0;
}

/* Success theme */
.tooltip-success::after {
    background: #27ae60;
}
.tooltip-success::before {
    border-top-color: #27ae60;
}

/* Warning theme */
.tooltip-warning::after {
    background: #f39c12;
    color: #333;
}
.tooltip-warning::before {
    border-top-color: #f39c12;
}

Accessibility Consideration

.tooltip:focus::after,
.tooltip:focus::before {
    opacity: 1;
}

.tooltip:focus::after {
    transform: translateX(-50%) translateY(0);
}

Adding :focus alongside :hover ensures keyboard users can also see the tooltip when they tab to the element.

Key Takeaways

  • content: attr(data-tooltip) pulls tooltip text from an HTML data attribute into a pseudo-element — no JavaScript needed
  • The ::after pseudo-element creates the tooltip bubble, and ::before creates the arrow
  • CSS triangles are made with the border trick — transparent borders on three sides, colored on one side
  • Combining opacity and transform transitions creates a smooth fade + slide entrance effect
  • pointer-events: none prevents the tooltip from interfering with hover states on elements below it
  • Add :focus styles alongside :hover for keyboard accessibility