How it works
Frosted glass, layer by layer
The glass is a stack of five cheap layers. None of them looks like much on its own; together they read as a physical material.
The stack, back to front
- Backdrop. Whatever is rendered behind the panel. The panel never copies it; the browser reads it while compositing.
- Backdrop filter. Warps, blurs and color-corrects that backdrop, but only
inside the panel's border box (clipped to its
border-radius). - Tint. A translucent
backgroundcolor on the panel itself. It also decides the text color (dark on light tints, light on dark ones), and its opacity is what keeps that text readable: the default of 0.35 is the lowest value that keeps body text at 4.5:1 or better over every backdrop on this page. - Grain. A faint noise layer, the same component as on the noise page.
- Rim and glow. A one-pixel gradient border plus an inset shadow, which sell the edge of a slab of glass.
1. backdrop-filter: blur, saturate, brighten
backdrop-filter takes the same functions as filter, but applies
them to what is behind the element. The functions run left to right, each on the result
of the previous one:
- blur()
- The frost itself. Higher values erase more detail. The blur is clipped to the panel, so the edges stay sharp.
- saturate()
- Blurring averages neighboring colors, which drifts them towards grey. Pushing saturation back up (around 1.4–1.8) keeps the panel looking lit from behind instead of muddy.
- brightness()
- A slight lift reads as light passing through the glass.
2. Displacement: feTurbulence → feDisplacementMap
Real frosted or textured glass doesn't just blur, it refracts: the backdrop looks shifted
and wavy. backdrop-filter also accepts url(#id), a reference to an
SVG filter, and the browser runs the backdrop through it like any image. The filter has two
steps:
<feTurbulence type="fractalNoise">generates a smooth noise image (the map). A low base frequency gives large, gentle waves; a high one gives a pebbled, rippled texture.<feDisplacementMap>moves every pixel of the backdrop by an amount read from the map: the red channel moves it horizontally, the green channel vertically. A channel value of 0.5 means no movement; 0 and 1 mean half ofscalepixels in either direction. In formula form:P′(x, y) = P(x + scale·(R − ½), y + scale·(G − ½)).
The displacement comes first in the chain, so the following blur smooths out the jagged edges that pixel-shifting creates. Pixels near the border that would be fetched from outside the panel come out transparent; the rim hides that seam.
<svg width="0" height="0" aria-hidden="true">
<filter id="displace" x="0" y="0" width="100%" height="100%"
color-interpolation-filters="sRGB">
<feTurbulence type="fractalNoise" baseFrequency="0.008"
numOctaves="2" seed="7" result="map" />
<feDisplacementMap in="SourceGraphic" in2="map" scale="40"
xChannelSelector="R" yChannelSelector="G" />
</filter>
</svg> 3. The rim: a masked gradient border
A plain border can't fade along its length. Instead, a ::after pseudo-element covers the panel, gets padding equal to the rim width and a linear-gradient background. Then two masks are combined with exclude: one covering the whole box and one covering only the content box. What
remains visible is just the padding, a gradient-colored ring that follows the border-radius.
The gradient is bright at one end, nearly transparent in the middle and brighter again at
the other end: a highlight where the light hits, plus a weaker reflection on the opposite
edge. The Light angle slider is the gradient's angle. An inset box-shadow adds the
inner glow, a regular one the drop shadow.
.glass {
position: relative;
border-radius: 28px;
background: rgb(255 255 255 / 0.35);
backdrop-filter: url(#displace) blur(14px) saturate(1.6) brightness(1.05);
}
/* the rim: a gradient that only shows in the padding box */
.glass::after {
content: '';
position: absolute;
inset: 0;
padding: 1px;
border-radius: inherit;
background: linear-gradient(135deg,
rgb(255 255 255 / 0.7), transparent 35% 65%,
rgb(255 255 255 / 0.42));
mask: linear-gradient(#000 0 0) content-box exclude,
linear-gradient(#000 0 0);
pointer-events: none;
} 4. Grain
Large blurs over smooth gradients show visible color banding. A very faint noise layer
blended with soft-light dithers those bands away and gives the surface a tactile,
matte quality. It sits above the tint and below the content, and inherits the panel's rounded
corners.
Browser support
Blur, tint, grain and rim work in all current browsers. The displacement renders in
Chromium-based browsers only. Firefox (tested in 156) accepts backdrop-filter: url(#…) blur(…) but then renders no backdrop filter at all: no
displacement and no blur, just a transparent panel. Safari is untested. Feature detection
doesn't help: CSS.supports() only checks the syntax and says yes everywhere. So
the component leaves url() out unless it runs in Chromium, and other browsers
keep the frost. On this page you can see the failure for yourself with Try it anyway.
Cost: the backdrop filter is re-evaluated whenever anything behind the panel changes, such as the animated backdrop on this page. Large blur radii and strong displacement over big areas are the expensive parts; small panels are cheap.