back

Progressive blur

A single backdrop-filter: blur(12px) gives you a panel of frosted glass with a hard edge. You can see the blur, you can see the content, and you can see a seam where one ends and the other begins.

Progressive blur removes the seam. The effect ramps across a region - sharp at one edge, heavily blurred at the other - so the surface feels like it grew out of the content instead of being stamped on top. Apple uses this on the iOS lock screen, under Music's now-playing controls, and around sheet modals.

The trick

backdrop-filter takes one number. It can't vary by position. But you can stack several blurred layers on top of each other and use mask-image to decide where each layer shows up. Light blurs reach further, heavy blurs concentrate at the anchor edge, and the composite ramps from nothing to heavy blur across the region.

<div class="blur-stack">
  <div class="layer" data-blur="1"></div>
  <div class="layer" data-blur="2"></div>
  <div class="layer" data-blur="4"></div>
  <div class="layer" data-blur="8"></div>
  <div class="layer" data-blur="16"></div>
</div>
.blur-stack {
  position: absolute;
  inset: 0 0 auto 0;
  height: 140px;
  pointer-events: none;
}

.blur-stack .layer {
  position: absolute;
  inset: 0;
}

.blur-stack .layer[data-blur="1"] {
  backdrop-filter: blur(1px);
  mask-image: linear-gradient(to top, transparent 0%, black 50%);
}
.blur-stack .layer[data-blur="2"] {
  backdrop-filter: blur(2px);
  mask-image: linear-gradient(to top, transparent 15%, black 60%);
}
.blur-stack .layer[data-blur="4"] {
  backdrop-filter: blur(4px);
  mask-image: linear-gradient(to top, transparent 30%, black 72%);
}
.blur-stack .layer[data-blur="8"] {
  backdrop-filter: blur(8px);
  mask-image: linear-gradient(to top, transparent 50%, black 85%);
}
.blur-stack .layer[data-blur="16"] {
  backdrop-filter: blur(16px);
  mask-image: linear-gradient(to top, transparent 70%, black 100%);
}

Every layer is opaque at the top and fades to transparent at its own depth. The heaviest blur (16px) is opaque only near the top. The lightest blur (1px) reaches all the way to the bottom and dissolves to nothing there.

That last part matters. If every layer faded out somewhere above the bottom edge, you would see a line where the blur ends and the content becomes sharp again. By letting the weakest blur fade to zero at the edge, the ramp ends in a real nothing, not a cutoff.

Don't fade to transparent

transparent in CSS is literally rgba(0, 0, 0, 0) - black with zero alpha. Gradients that interpolate from a color to transparent cross through black-tinted midpoints. On dark pages you can't see it. On anything else, your "clean fade" looks slightly dirty.

Fade to the same color with zero alpha instead:

/* Bad - tints through black */
background: linear-gradient(to bottom, white, transparent);

/* Good - clean fade */
background: linear-gradient(to bottom, white, rgb(255 255 255 / 0));

Tailwind users can write to-white/0 for the same result.

Direction matters

The anchor edge decides which way to run your masks. A sticky header wants the strongest blur at the top, so masks go to top. A long paragraph that fades out at the bottom wants the strongest blur there, so masks go to bottom. A horizontal carousel with blurred edges uses to left and to right with two separate stacks.

Flip the direction and flip which end "opaque" lands on. The technique is the same.

Gotchas

A gradient of blur, not a gradient of color. That's the whole trick.