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.
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.
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.
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.
overflow: hidden. Multiple
stacked backdrop-filter layers inside an ancestor with
overflow: hidden render incorrectly on Chromium. Clip the
rounded corners on each layer directly with border-radius,
not on a wrapper.
mask-image unprefixed, but older Safari needs
-webkit-mask-image in addition. Same for
backdrop-filter / -webkit-backdrop-filter.
backdrop-filter samples
whatever is painted behind the element at render time. Over a sticky
header this is the scrolling content, which is exactly what you want.
A gradient of blur, not a gradient of color. That's the whole trick.