Originally published at webtricks.dev, where the liquid glass card snippet has a live playground.
Apple's Liquid Glass (introduced across iOS, iPadOS and macOS in 2025) is
the current reference for translucent UI. The good news: around 80% of the look
is plain, well-supported CSS. The catch: the bending refraction — where the
background visibly warps as it passes through the glass — needs an SVG
displacement filter that, today, only Chromium can feed into backdrop-filter.
This guide builds the effect in layers so you can stop at the layer your
support target allows. Every block is copy-paste ready; the control-driven
version lives in the liquid glass card snippet.
What "liquid glass" actually is
Look at a Liquid Glass control up close and you can separate five layers.
Each maps to one CSS feature:
| Layer | What it does | CSS |
|---|---|---|
| Tint | A faint white (or black) wash so the pane reads as a material, not a hole | background: rgba(255,255,255,.1) |
| Blur + saturation | Frosts what is behind and pumps its colors so the glass looks thick | backdrop-filter: blur() saturate() |
| Specular rim | A thin bright edge, strongest at the top, where light enters the pane | inset box-shadow highlights + a 1px border |
| Sheen | A soft diagonal gloss across the surface | a ::after with a linear-gradient and mix-blend-mode: screen
|
| Refraction | The backdrop bends near the edges and ripples slightly |
backdrop-filter: url(#filter) with feDisplacementMap
|
Classic glassmorphism (the 2020 trend) is layers one to three. Liquid
glass adds the sheen and refraction, and uses stronger saturation so the
material looks lit from inside. If you only need the simpler flat version,
the glassmorphism card snippet is enough.
1. The cross-browser core (liquid glass CSS only)
Frosted glass is backdrop-filter. Liquid glass reads as a thick, lit pane,
and three ingredients do that: tint, blur with saturation, and a rim built
from inset shadows. This block works in every current browser:
.liquid-glass {
position: relative;
isolation: isolate; /* keeps the ::after's z-index inside this box */
padding: 28px;
border-radius: 28px;
color: #fff;
/* 1. tint */
background: rgba(255, 255, 255, 0.10);
/* 2. frost — blur + saturation + a touch of brightness */
backdrop-filter: blur(8px) saturate(180%) brightness(1.1);
-webkit-backdrop-filter: blur(8px) saturate(180%) brightness(1.1);
/* 3. specular rim: strong top highlight, softer on the other edges */
border: 1px solid rgba(255, 255, 255, 0.18);
box-shadow:
0 8px 32px rgba(0, 0, 0, 0.25), /* lift */
inset 0 1px 1px rgba(255, 255, 255, 0.55), /* top */
inset 0 -1px 1px rgba(255, 255, 255, 0.30), /* bottom */
inset 1px 0 1px rgba(255, 255, 255, 0.20), /* left */
inset -1px 0 1px rgba(255, 255, 255, 0.20); /* right */
}
Then add the diagonal sheen with a pseudo-element, so the surface looks
glossy rather than matte:
.liquid-glass::after {
content: "";
position: absolute;
inset: 0;
z-index: -1; /* below the text, above the tint */
border-radius: inherit;
pointer-events: none;
background: linear-gradient(
135deg,
rgba(255, 255, 255, 0.45) 0%,
rgba(255, 255, 255, 0.08) 28%,
transparent 58%
);
mix-blend-mode: screen;
}
Why the asymmetric rim? Real glass catches light from above, so the top
inset is roughly twice as bright as the sides. Make all four equal and the
card flattens into an outlined box. If you want to tune the lift shadow and
the inset highlights visually, the box-shadow generator
builds layered inset shadows and copies the whole declaration.
A complete, working liquid glass card then looks like this:
<div class="liquid-glass">
<h3>Liquid glass</h3>
<p>Frosted, lit from the top, glossy across the surface.</p>
</div>
Put it over a busy, colorful background — a photo, or a few overlapping
radial gradients. Over a flat fill the blur has nothing to sample and the
effect vanishes (see Common mistakes below).
2. Light-mode variant
On a light background, white tint plus white highlights disappear. Flip the
tint toward the background color, lower the saturation boost, and switch the
rim to a mix of white (top) and dark (bottom) so the edge still reads:
.liquid-glass.light {
color: #111;
background: rgba(255, 255, 255, 0.45);
backdrop-filter: blur(14px) saturate(140%);
-webkit-backdrop-filter: blur(14px) saturate(140%);
border: 1px solid rgba(255, 255, 255, 0.65);
box-shadow:
0 8px 24px rgba(0, 0, 0, 0.10),
inset 0 1px 1px rgba(255, 255, 255, 0.90),
inset 0 -1px 1px rgba(0, 0, 0, 0.06);
}
.liquid-glass.light::after {
background: linear-gradient(
135deg,
rgba(255, 255, 255, 0.70) 0%,
rgba(255, 255, 255, 0.15) 30%,
transparent 60%
);
mix-blend-mode: normal; /* screen does nothing useful over white */
}
Two rules of thumb for light mode: more blur (12–16px) because light
backgrounds have less contrast to frost, and a higher tint alpha (0.4–0.6)
because the text needs a more opaque surface to stay readable.
If you theme with tokens, drop your brand color into the
color converter to get it as rgb() so you can
append an alpha: rgb(14 165 233 / 0.12) makes a tinted glass in one line.
3. Real refraction with SVG displacement
To make the backdrop actually bend, displace it with an SVG filter.
Procedural noise from feTurbulence drives an feDisplacementMap:
<svg width="0" height="0" aria-hidden="true" style="position:absolute">
<filter id="liquid" x="-20%" y="-20%" width="140%" height="140%">
<feTurbulence type="fractalNoise" baseFrequency="0.008 0.012"
numOctaves="2" seed="9" result="noise" />
<feGaussianBlur in="noise" stdDeviation="1.4" result="soft" />
<feDisplacementMap in="SourceGraphic" in2="soft" scale="40"
xChannelSelector="R" yChannelSelector="G" />
</filter>
</svg>
@supports (backdrop-filter: url('#liquid')) {
.liquid-glass {
backdrop-filter: blur(4px) saturate(180%) brightness(1.1) url('#liquid');
}
}
How it works: feTurbulence produces an RGB noise image. feDisplacementMap
reads the red channel to shift each backdrop pixel horizontally and the
green channel to shift it vertically. A channel value of 0.5 means "no
move", so values above and below push pixels in opposite directions, and
scale sets how far (in user units) a full-strength value can move a pixel.
The feGaussianBlur in between softens the noise so the distortion looks
like water, not static.
Tuning the three numbers
| Attribute | Low values | High values | Good range for glass |
|---|---|---|---|
baseFrequency |
Large, slow waves (0.002 — a single swell across the card) | Fine ripples (0.05+ — looks like frosted texture, not liquid) | 0.005–0.015; use two values (x y) for a slightly stretched, less "random" wobble |
numOctaves |
1 — smooth, blobby | 4+ — adds fine detail on top of the big waves, much more expensive | 1–2 |
scale |
5–15 — barely visible shimmer | 80+ — the backdrop tears into chunks | 20–50 |
Change seed to get a different noise layout with the same character. The
filter region (x="-20%" etc.) is widened on purpose: displacement pulls
pixels in from outside the element, and with the default region the edges
clip to hard lines.
A static alternative: feImage
Noise gives you an organic, random wobble. Apple's real effect is not random:
the distortion is strongest at the edges (like a lens) and flat in the middle.
You get that by replacing the turbulence with a hand-made displacement
map — a PNG or inline SVG where the red/green channels encode the push
direction, grey in the center, ramping toward the edges:
<filter id="lens" x="0" y="0" width="100%" height="100%">
<feImage href="lens-map.png" preserveAspectRatio="none"
result="map" />
<feDisplacementMap in="SourceGraphic" in2="map" scale="32"
xChannelSelector="R" yChannelSelector="G" />
</filter>
The map is stretched to the element with preserveAspectRatio="none", so one
image fits any card size. Paint it with a radial gradient from
rgb(128,128,128) in the middle to red/green-shifted corners, or generate it
from a small canvas script. This is the technique the in-depth walkthroughs at
LogRocket
and this Habr article build up to,
and it is much closer to the native look than raw noise.
Browser support
Two separate features matter: backdrop-filter itself, and whether a browser
accepts an SVG filter reference (url(#id)) inside backdrop-filter.
| Feature | Chrome / Edge | Safari | Firefox |
|---|---|---|---|
backdrop-filter (blur, saturate…) |
76+ | 9+ with -webkit- prefix, 18+ unprefixed |
103+ |
@supports (backdrop-filter: …) |
yes | yes | yes |
url(#svg-filter) inside backdrop-filter
|
yes | no (declaration is dropped) | not reliable — treat as no |
url(#svg-filter) inside plain filter
|
yes | yes | yes |
prefers-reduced-transparency media query |
118+ | no | behind a flag |
The practical consequence: keep -webkit-backdrop-filter for older Safari,
and always gate the SVG refraction behind
@supports (backdrop-filter: url('#id')). Browsers that fail the check keep
the frosted layer, which already looks good. Do not combine the two in one
unguarded declaration — Safari rejects the whole backdrop-filter value if
any part of it is invalid, and you lose the blur too.
Accessibility
-
Contrast is unpredictable. Text over glass sits on whatever scrolls
behind it. WCAG's 4.5:1 ratio must hold against the worst part of the
backdrop, not the average. Raise the tint alpha, add a subtle
text-shadow: 0 1px 2px rgb(0 0 0 / .4), or keep glass for chrome (toolbars, pills) and put long text on solid surfaces. - Respect reduced transparency. Users who turn on "Reduce transparency" in macOS or iOS expect opaque panels:
@media (prefers-reduced-transparency: reduce) {
.liquid-glass {
background: rgb(28 30 38 / 0.92);
backdrop-filter: none;
-webkit-backdrop-filter: none;
}
}
-
Reduced motion. If you animate the displacement (for instance by
animating
baseFrequencywith an SVGanimateelement), disable it underprefers-reduced-motion: reduce. A rippling backdrop behind text is a real problem for readers with vestibular disorders.
Performance
backdrop-filter forces the browser to composite everything behind the
element into a texture, blur it, and re-composite every frame something
behind it moves. Three rules keep it smooth:
- Keep it off scrolling lists. One glass header over a scrolling page is fine; 50 glass cards inside a scrolling list means 50 blurs per frame. Use glass for the container, not the items.
-
Blur radius is the cost knob.
blur(8px)is cheap;blur(40px)on a full-width panel can drop a mid-range phone below 60fps. Liquid glass wants a small blur (4–10px) anyway — the saturation does the work. -
SVG displacement is the expensive layer.
feTurbulencewithnumOctaves="2"recomputed every frame on a large element is noticeably heavier than any CSS filter. Keep displaced elements small (buttons, pills, a single card), keepnumOctavesat 1–2, and never animate the noise on more than one element.
will-change: backdrop-filter is rarely useful: the element is already
promoted to its own compositor layer by the filter. Adding will-change: to a glass element that moves (a draggable card, a slide-in
transform
sheet) does help, because it stops the browser re-rasterizing the pane
while it moves.
Common mistakes
- Flat background. The most frequent "it doesn't work" report. Glass only shows over variation. Test over a photo or stacked radial gradients first.
-
Forgetting
isolation: isolate. The sheen usesz-index: -1. Without its own stacking context, the pseudo-element drops behind the parent's background and disappears — or, worse, behind the page. -
::aftercovering the text. If you setz-index: 1instead, the gloss sits on top of the text and washes it out. Keep the sheen at-1and the text at auto. -
Missing
position: relative. Theinset: 0on the pseudo-element is relative to the nearest positioned ancestor; without it the sheen stretches over the whole page. -
overflow: hiddenon the glass element. It clips the outer lift shadow. Useborder-radius: inheriton children instead. - Equal highlights on all sides. Reads as an outline, not a rim. Keep the top highlight the strongest.
- Blur too strong. Beyond ~16px the backdrop turns into a uniform smear and the material reads as "frosted plastic", not glass.
FAQ
Can I do liquid glass with CSS only, no SVG?
Yes. Tint, blur, saturation, the specular rim and the sheen are all plain
CSS and work in every current browser. You only lose the refraction (the
visible bending of the backdrop), which is the one part that needs an SVG
feDisplacementMap and currently works only in Chromium.
What is the difference between glassmorphism and liquid glass?
Glassmorphism is a translucent tint plus a blur and a thin border. Liquid
glass adds strong saturation, an asymmetric specular rim, a glossy sheen and
edge refraction so the panel looks like a thick lens rather than frosted
film. Compare the glassmorphism card and the
liquid glass card side by side.
Why does my backdrop-filter not work in Safari?
Older Safari needs the -webkit-backdrop-filter prefix (unprefixed support
arrived in Safari 18). Also, if any part of the value is unsupported — such
as url(#filter) — Safari discards the whole declaration, so put the SVG
part inside @supports.
Does liquid glass hurt performance?
A single glass element with a small blur is cheap. The cost scales with the
element's area, the blur radius, and how often what is behind it changes.
Avoid glass on many list items, keep the blur under ~10px, and reserve the
SVG displacement for small controls.
How do I make the glass tinted with my brand color?
Replace the white tint with your color at low alpha, for example
background: rgb(14 165 233 / 0.14), and keep the highlights white. Use the
color converter to get your hex as space-separated
rgb() so you can add the alpha channel.
Try it live
Open the liquid glass card snippet and drag
the blur, saturation, radius and tint controls, then copy the CSS with your
settings. Browse all snippets or subscribe via RSS.
Top comments (1)
The reduced-transparency fallback still uses 92% opacity. A fully opaque background would remove that remaining variation behind toolbar icons.