Transitioning Visibility with Opacity in CSS

Learn how to combine visibility and opacity transitions to properly hide elements. Solves the common problem of invisible-but-clickable elements.

Performance Tips

Detailed Explanation

The Visibility + Opacity Pattern

A common mistake is using opacity: 0 alone to hide an element. The problem: an element with opacity: 0 is invisible but still occupies space and receives click events. The solution is to pair opacity with visibility.

The Problem

/* Bad: element is invisible but still clickable */
.tooltip {
  opacity: 0;
  transition: opacity 0.3s ease;
}
.trigger:hover .tooltip {
  opacity: 1;
}

The Solution

.tooltip {
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.3s ease,
              visibility 0.3s;
}

.trigger:hover .tooltip {
  opacity: 1;
  visibility: visible;
}

How Visibility Transitions Work

The visibility property does not interpolate like numeric values. It uses a step function:

  • Showing (hidden → visible): visibility switches to visible at the start of the transition, allowing the opacity fade-in to be visible
  • Hiding (visible → hidden): visibility switches to hidden at the end of the transition, after the opacity fade-out completes

This behavior is exactly what you want: the element becomes interactive (visible) immediately when showing, and becomes non-interactive (hidden) only after the fade-out finishes.

Alternative: pointer-events

Another approach uses pointer-events: none to prevent clicks on invisible elements:

.tooltip {
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s ease;
}

.trigger:hover .tooltip {
  opacity: 1;
  pointer-events: auto;
}

This keeps the element in the accessibility tree (screen readers may still read it), whereas visibility: hidden removes it from both visual display and accessibility.

Use Case

The visibility + opacity pattern is essential for tooltips, dropdown menus, modal overlays, popover components, and any UI element that must be fully hidden (not just invisible) when closed.

Try It — CSS Transition Generator

Open full tool