What CSS opacity is

The opacity property controls how transparent an element is. It affects the entire element: its background, its text, its children, its borders, everything. Think of it like turning down the “visibility” knob for the whole layer.

If you only want the background to be transparent (but keep the text crisp and fully opaque), don’t reach for opacity first — you’ll usually want an alpha color (rgba(), hsl() with alpha, or hex with alpha), or a pseudo-element trick. We’ll do both in this tutorial.

CSS opacity property and values

opacity accepts a number from 0 to 1:

  • 1 means fully opaque (default).
  • 0 means fully transparent (it still takes up space unless you also change layout).
  • Values like 0.5 are 50% opacity.

Important beginner gotcha: opacity applies to the element and all its descendants as a single “group”. If you set a parent to opacity: 0.5;, the children are also faded. There is no “un-fade the child” spell.

Interactive: opacity values (0 to 1)

Drag the slider to set the opacity of the card.

.card {
  opacity: 0.85;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.wrap {
  max-width: 980px;
  padding: 18px;
  font-family: system-ui, Arial, sans-serif;
}

.card {
  border: 3px solid #111;
  border-radius: 16px;
  padding: 16px;
  background: #fff;
  display: grid;
  gap: 10px;
  box-shadow: 0 12px 0 #111;
}

.card h4 {
  margin: 0;
  font-size: 18px;
}

.card p {
  margin: 0;
  line-height: 1.5;
}

.pills {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.pill {
  padding: 6px 10px;
  border: 2px solid #111;
  border-radius: 999px;
  background: #f2f2f2;
  font-size: 13px;
}
  

Opacity demo card

Opacity fades everything inside this card: background, text, pills, borders.

Child Elements Fade too

Opacity gotchas: clickability, layering, and “invisible but still there”

  • Opacity doesn’t remove layout. An element with opacity: 0; is invisible, but it still takes up space.
  • Opacity can still capture clicks. An invisible element can block interactions underneath. If you’re fading something out for UI states, you often pair opacity with pointer-events: none;.
  • Opacity creates a stacking context. This matters when you’re using z-index with overlapping elements.

Example: invisible but blocking clicks

In the first snippet, the faded overlay still blocks clicks. In the second snippet, we add pointer-events: none; so clicks go through it.

.overlay {
  opacity: 0.35;
}
  
.overlay {
  opacity: 0.35;
  pointer-events: none;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.wrap {
  max-width: 980px;
  padding: 18px;
  font-family: system-ui, Arial, sans-serif;
  display: grid;
  gap: 14px;
}

.stage {
  position: relative;
  border: 3px solid #111;
  border-radius: 16px;
  overflow: hidden;
  padding: 16px;
  background: #fff;
  min-height: 160px;
  display: grid;
  gap: 12px;
}

.actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: center;
}

.button {
  display: inline-block;
  padding: 10px 12px;
  border: 3px solid #111;
  border-radius: 12px;
  background: #f2f2f2;
  font-weight: 700;
  font-size: 14px;
}

.note {
  margin: 0;
  opacity: 0.8;
  line-height: 1.5;
}

.overlay {
  position: absolute;
  inset: 0;
  background: linear-gradient(120deg, #0077b6 0%, #90e0ef 55%, #caf0f8 100%);
}
  
Try clicking me

If the overlay blocks clicks, the link feels “dead”. Add pointer-events: none; to fix it.

CSS opacity transition

Transitions are great when you want a gentle fade between two states (like hover, focus, or a class toggle). The most common pattern is: start at opacity: 1, fade to something lower on hover, and add a transition so it doesn’t snap.

Pro tip: if you ever animate opacity for UI, consider prefers-reduced-motion so users who prefer less motion don’t get forced fades everywhere.

Opacity transition on hover

.card:hover {
  opacity: 0.55;
}
  
.card:hover {
  opacity: 0.55;
}

.card {
  transition: opacity 220ms ease;
}
  
.card:hover {
  opacity: 0.55;
}

.card {
  transition: opacity 220ms ease;
}

@media (prefers-reduced-motion: reduce) {
  .card {
    transition: none;
  }
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.wrap {
  max-width: 980px;
  padding: 18px;
  font-family: system-ui, Arial, sans-serif;
}

.card {
  border: 3px solid #111;
  border-radius: 16px;
  padding: 16px;
  background: #fff;
  display: grid;
  gap: 10px;
  box-shadow: 0 12px 0 #111;
}

.card h4 {
  margin: 0;
  font-size: 18px;
}

.card p {
  margin: 0;
  line-height: 1.5;
  opacity: 0.85;
}

.hint {
  margin: 0;
  font-size: 13px;
  opacity: 0.8;
}
  

Hover me (or focus me)

Without a transition, opacity changes instantly. With a transition, it fades smoothly.

Try hovering the card with your mouse.

CSS opacity animation

Animations are best when you want opacity to keep changing over time (like pulsing, blinking, loading indicators, or attention cues). Most opacity animations use @keyframes.

  • Animate opacity for small emphasis (avoid rapid blinking, it’s annoying and can be unsafe).
  • Prefer small ranges (like 1 down to 0.6) for “pulse” effects.
  • Combine with prefers-reduced-motion for accessibility.

Opacity pulse animation

@keyframes pulse {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.55;
  }
}

.badge {
  animation: pulse 1.1s ease-in-out infinite;
}

@media (prefers-reduced-motion: reduce) {
  .badge {
    animation: none;
  }
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.wrap {
  max-width: 980px;
  padding: 18px;
  font-family: system-ui, Arial, sans-serif;
  display: grid;
  gap: 14px;
}

.row {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: center;
}

.card {
  border: 3px solid #111;
  border-radius: 16px;
  padding: 16px;
  background: #fff;
  box-shadow: 0 12px 0 #111;
  display: grid;
  gap: 10px;
  min-width: 280px;
}

.card p {
  margin: 0;
  opacity: 0.85;
  line-height: 1.5;
}

.badge {
  display: inline-block;
  padding: 8px 12px;
  border: 3px solid #111;
  border-radius: 999px;
  background: #f2f2f2;
  font-weight: 800;
  font-size: 13px;
  width: max-content;
}
  
New

This badge pulses by animating opacity.

Live

Keep it subtle: small fades feel polished, big fades feel like a disco sign.

CSS Opacity Background

This is where beginners get tricked most often: opacity affects the entire element. So if you set opacity on a card just to fade its background, you will also fade the text.

Let’s handle background opacity the right way, depending on what kind of “background” you might want.

CSS Opacity Background Color

If you only want a semi-transparent background color, use an alpha color instead of opacity. That keeps the text fully opaque.

  • rgba(0, 0, 0, 0.25) is classic.
  • hsl(210 90% 40% / 0.25) is modern and very readable.
  • Hex alpha like #00000040 is also possible (the last two digits are the alpha).
.card {
  background: rgba(0, 119, 182, 0.18);
}
  
.card {
  background: hsl(200 90% 45% / 0.18);
}
  
.card {
  background: #0077b62e;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.wrap {
  max-width: 980px;
  padding: 18px;
  font-family: system-ui, Arial, sans-serif;
}

.card {
  border: 3px solid #111;
  border-radius: 16px;
  padding: 16px;
  display: grid;
  gap: 10px;
  box-shadow: 0 12px 0 #111;
}

.card h4 {
  margin: 0;
  font-size: 18px;
}

.card p {
  margin: 0;
  line-height: 1.5;
  opacity: 0.9;
}
  

Transparent background, solid text

The background uses an alpha color. Notice the text stays crisp and fully opaque.

CSS Opacity of Background Image

You can’t set opacity on just the background image directly. If you do opacity: 0.4; on the element, you fade the text too.

The common solution is a pseudo-element (::before) that holds the background image. Then you set opacity on the pseudo-element, not on the content.

.hero {
  position: relative;
  overflow: hidden;
}

.hero::before {
  opacity: 0.35;
}

.hero::before {
  content: "";
  position: absolute;
  inset: 0;
  background: url("https://picsum.photos/1200/700") center / cover no-repeat;
}

.hero > * {
  position: relative;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.wrap {
  max-width: 980px;
  padding: 18px;
  font-family: system-ui, Arial, sans-serif;
}

.hero {
  border: 3px solid #111;
  border-radius: 18px;
  padding: 22px;
  background: #fff;
  box-shadow: 0 12px 0 #111;
  display: grid;
  gap: 10px;
  min-height: 240px;
  align-content: end;
}

.hero h4 {
  margin: 0;
  font-size: 20px;
}

.hero p {
  margin: 0;
  max-width: 55ch;
  line-height: 1.55;
  opacity: 0.9;
}

.tag {
  width: max-content;
  padding: 7px 10px;
  border: 3px solid #111;
  border-radius: 999px;
  background: #f2f2f2;
  font-weight: 800;
  font-size: 12px;
}
  
Pseudo-element background

Background image fades, content stays solid

The image lives on ::before, so we can fade it independently with opacity.

Learn more about ::before in the CSS ::before and ::after Pseudo-Elements Interactive Tutorial.

CSS opacity gradient

Sometimes you don’t want a uniform transparency. You want a gradual fade, like an image that fades out to transparent.

That’s where CSS masking shines. A mask controls which parts of an element are visible. White areas in the mask show the element, black areas hide it, and gray gives partial transparency.

In practice, we often use mask-image: linear-gradient(...) (and the -webkit- prefixed version) to create a soft “opacity gradient” without editing the image.

Mask an image with a gradient fade

.media img {
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 65%, transparent 100%);
  mask-image: linear-gradient(to bottom, #000 0%, #000 65%, transparent 100%);
}
  
.media img {
  -webkit-mask-image: linear-gradient(to right, #000 0%, #000 60%, transparent 100%);
  mask-image: linear-gradient(to right, #000 0%, #000 60%, transparent 100%);
}
  
.media img {
  -webkit-mask-image: radial-gradient(circle at 40% 30%, #000 0%, #000 55%, transparent 72%);
  mask-image: radial-gradient(circle at 40% 30%, #000 0%, #000 55%, transparent 72%);
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.wrap {
  max-width: 980px;
  padding: 18px;
  font-family: system-ui, Arial, sans-serif;
  display: grid;
  gap: 14px;
}

.card {
  border: 3px solid #111;
  border-radius: 18px;
  overflow: hidden;
  background: #fff;
  box-shadow: 0 12px 0 #111;
}

.media {
  aspect-ratio: 16 / 9;
  background: #f2f2f2;
}

.media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.caption {
  padding: 14px 16px 16px 16px;
  display: grid;
  gap: 8px;
}

.caption h4 {
  margin: 0;
  font-size: 18px;
}

.caption p {
  margin: 0;
  opacity: 0.85;
  line-height: 1.55;
  max-width: 65ch;
}
  
Random scenic placeholder

Mask gradient fade

The image is faded using a mask gradient. The element is still “fully opaque” in CSS terms, but the mask makes part of it transparent.

CSS opacity fade

A super practical use-case: fading out the bottom of a text block so it hints there’s more content. This is common in cards, previews, and “read more” sections.

Again, we’ll use a mask. The text remains selectable and accessible (it isn’t replaced by an overlay), but it becomes gradually transparent near the bottom.

Fade out the bottom of content with a mask

.preview {
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 70%, transparent 100%);
  mask-image: linear-gradient(to bottom, #000 0%, #000 70%, transparent 100%);
}
  
.preview {
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 55%, transparent 92%);
  mask-image: linear-gradient(to bottom, #000 0%, #000 55%, transparent 92%);
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.wrap {
  max-width: 980px;
  padding: 18px;
  font-family: system-ui, Arial, sans-serif;
  display: grid;
  gap: 14px;
}

.card {
  border: 3px solid #111;
  border-radius: 18px;
  background: #fff;
  box-shadow: 0 12px 0 #111;
  padding: 16px;
  display: grid;
  gap: 10px;
}

.card h4 {
  margin: 0;
  font-size: 18px;
}

.preview {
  border: 2px solid #111;
  border-radius: 14px;
  padding: 14px;
  background: #f2f2f2;
  max-height: 140px;
  overflow: hidden;
}

.preview p {
  margin: 0;
  line-height: 1.6;
  opacity: 0.9;
}

.meta {
  margin: 0;
  font-size: 13px;
  opacity: 0.8;
}
  

Bottom fade preview

This uses a mask to fade the content near the bottom.

Opacity fades are useful when you want to hint that content continues without showing a hard cutoff. This technique is commonly used for text previews, descriptions in cards, and “read more” UI.

The mask doesn’t change layout, it just changes visibility. And because this is still real text, it stays selectable and accessible.

Learn more about CSS masks in the CSS Mask Interactive Tutorial.

Opacity vs other techniques

  • Use opacity when you truly want to fade the entire element (and its children) as a whole.
  • Use alpha colors (rgba(), hsl(... / ...), hex alpha) when you only want a transparent background color.
  • Use pseudo-elements when you want to fade a background image without fading content.
  • Use CSS masks when you want a gradient fade or a partial fade in a specific area.

Quick opacity checklist

  • Need to fade the whole component? Use opacity.
  • Need to fade only the background color? Use an alpha color.
  • Need to fade only the background image? Put it on ::before and set opacity there.
  • Need a gradient fade? Use mask-image (and include -webkit-mask-image for best coverage).
  • Fading UI in/out? Consider pointer-events and prefers-reduced-motion.

That’s opacity for you: a simple property with a few sneaky side effects. Once you know when to switch to alpha colors, pseudo-elements, or masks, you’ll stop fighting it and start using it like a pro.