---
name: liquid-glass-ui
description: Use when you want "Premium, futuristic, elegant" - Blends glass blur, transparency, refraction, and fluid motion.
---

# Liquid Glass UI

> **Category:** Premium / Futuristic  -  **Personality:** Premium, futuristic, elegant
>
> **Best use:** Luxury SaaS, AI products
>
> **Ratings:** Professional 5/5 - Casual 3/5 - Exotic 5/5 - Visual impact 5/5 - Difficulty 5/5 - Performance cost 4/5

## What it is

Apple-style "liquid glass": translucent panels that blur and saturate whatever flows behind them, but pushed past flat glassmorphism with three extra layers. An **SVG displacement filter** refracts the backdrop so light visibly bends through the slab; a **masked-gradient specular rim** lights up the opposing edges like the lit lip of real glass; and a **slowly flowing colour field** behind the panels makes the refraction shimmer. The chrome itself starts to feel like a physical material — exactly the mood luxury SaaS and AI control surfaces want.

## Dependencies / CDN

**None — pure CSS + inline SVG + a few lines of vanilla JS.** The demo loads three Google Fonts (optional, swap freely):

```html
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Unbounded:wght@500;600;700&family=Outfit:wght@300;400;500;600&family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet">
```

## HTML

The displacement filter lives once in the DOM; every glass panel references it. Glass and the colour backdrop must be **siblings** inside an `overflow:hidden` stage.

```html
<!-- the refraction filter (hidden defs) -->
<svg class="lg-defs" aria-hidden="true">
  <filter id="lg-refract" x="-25%" y="-25%" width="150%" height="150%" color-interpolation-filters="sRGB">
    <feTurbulence type="fractalNoise" baseFrequency="0.008 0.013" numOctaves="2" seed="14" result="n"/>
    <feGaussianBlur in="n" stdDeviation="1.3" result="nb"/>
    <feDisplacementMap in="SourceGraphic" in2="nb" scale="38" xChannelSelector="R" yChannelSelector="G"/>
  </filter>
</svg>

<div class="lg-stage">
  <!-- flowing colour backdrop (the thing the glass refracts) -->
  <div class="lg-flow" aria-hidden="true">
    <span class="lg-blob lg-c1"></span><span class="lg-blob lg-c2"></span><span class="lg-blob lg-c3"></span>
    <span class="lg-streak lg-s1"></span><span class="lg-streak lg-s2"></span>
  </div>

  <!-- a glass panel -->
  <div class="lg-glass lg-panel">
    <div class="lg-glow" aria-hidden="true"></div>     <!-- pointer-tracked specular -->
    <div class="lg-pbody">… your content …</div>
  </div>
</div>
```

## CSS

```css
/* ---- THE GLASS: blur + transparency + SVG displacement + specular rim ---- */
.lg-glass{
  position:relative; border-radius:24px;
  background:linear-gradient(150deg,rgba(255,255,255,.13),rgba(255,255,255,.035) 58%,rgba(255,255,255,.07));
  -webkit-backdrop-filter:blur(7px) saturate(165%) brightness(1.05);          /* Safari: no url() */
          backdrop-filter:blur(7px) saturate(165%) brightness(1.05) url(#lg-refract);  /* +refraction */
  box-shadow:0 20px 52px -20px rgba(3,2,14,.72),
             inset 0 1px 1.5px rgba(255,255,255,.5),       /* top inner highlight */
             inset 0 -12px 28px -20px rgba(255,255,255,.24);/* slab thickness */
}
/* specular rim — a gradient painted ONLY on a 1.4px border, bright on opposing corners */
.lg-glass::before{
  content:""; position:absolute; inset:0; border-radius:inherit; padding:1.4px; pointer-events:none; z-index:3;
  background:linear-gradient(140deg,rgba(255,255,255,.96),rgba(255,255,255,.06) 30%,rgba(255,255,255,0) 60%,rgba(255,255,255,.52));
  -webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);
  -webkit-mask-composite:xor; mask-composite:exclude;     /* keep only the border ring */
}
/* graceful fallback where backdrop-filter is unsupported */
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  .lg-glass{background:rgba(19,14,40,.84)}
}

/* ---- the flowing colour backdrop ---- */
.lg-stage{position:relative; overflow:hidden; border-radius:30px; isolation:isolate;
  background:radial-gradient(125% 130% at 50% -12%,#160d33,#0b0720 46%,#050310); min-height:660px}
.lg-flow{position:absolute; inset:-12%; z-index:0; transition:transform .35s ease-out}
.lg-blob{position:absolute; border-radius:50%; filter:blur(58px); opacity:.85}
.lg-c1{width:50%;height:64%;left:-9%;top:-14%;background:#7c3aed;animation:lg-f1 17s ease-in-out infinite}
.lg-c2{width:54%;height:66%;right:-11%;top:-7%;background:#22d3ee;animation:lg-f2 21s ease-in-out infinite}
.lg-c3{width:48%;height:62%;right:5%;bottom:-18%;background:#ec4899;animation:lg-f3 19s ease-in-out infinite}
/* thin bright streaks so the edge refraction visibly bends */
.lg-streak{position:absolute;left:18%;width:64%;height:2px;
  background:linear-gradient(90deg,transparent,rgba(255,255,255,.85),transparent)}

/* pointer-tracked highlight inside the panel (content sits above it) */
.lg-glow{position:absolute; inset:0; border-radius:inherit; z-index:0; pointer-events:none;
  background:radial-gradient(240px 240px at var(--mx,28%) var(--my,-6%),rgba(255,255,255,.18),transparent 66%)}
.lg-pbody{position:relative; z-index:1}

@keyframes lg-f1{0%,100%{transform:translate3d(0,0,0) scale(1)}50%{transform:translate3d(6%,-7%,0) scale(1.12)}}
@keyframes lg-f2{0%,100%{transform:translate3d(0,0,0) scale(1)}50%{transform:translate3d(-7%,5%,0) scale(1.1)}}
@keyframes lg-f3{0%,100%{transform:translate3d(0,0,0) scale(1.05)}50%{transform:translate3d(5%,-6%,0) scale(.94)}}

/* reduced motion = fully static */
@media (prefers-reduced-motion: reduce){
  .lg-blob,.lg-streak{animation:none!important}
  .lg-flow{transition:none}
}
```

## JavaScript

Optional — the effect is complete in CSS. JS only adds pointer parallax + a specular highlight that follows the cursor, and is gated behind `prefers-reduced-motion`.

```js
(function(){
  var stage=document.querySelector('.lg-stage'),
      flow =document.querySelector('.lg-flow'),
      panel=document.querySelector('.lg-panel');
  if(!stage) return;
  if(window.matchMedia && matchMedia('(prefers-reduced-motion: reduce)').matches) return;

  stage.addEventListener('pointermove',function(e){
    var r=stage.getBoundingClientRect();
    var nx=(e.clientX-r.left)/r.width-0.5, ny=(e.clientY-r.top)/r.height-0.5;
    flow.style.transform='translate3d('+(nx*26).toFixed(1)+'px,'+(ny*26).toFixed(1)+'px,0)'; // backdrop parallax
    if(panel){
      var p=panel.getBoundingClientRect();
      panel.style.setProperty('--mx',(((e.clientX-p.left)/p.width)*100).toFixed(1)+'%');     // move the glow
      panel.style.setProperty('--my',(((e.clientY-p.top )/p.height)*100).toFixed(1)+'%');
    }
  });
  stage.addEventListener('pointerleave',function(){ flow.style.transform=''; });
})();
```

## How it works

- **`backdrop-filter: blur() saturate()`** frosts and punches up the colour bleeding through the panel — the glassmorphism base.
- **`url(#lg-refract)`** chained into `backdrop-filter` runs that blurred backdrop through `feTurbulence` → `feDisplacementMap`, which nudges each backdrop pixel by the noise pattern. That nudge **is** the refraction — light appears to bend through the slab. Crucially, `backdrop-filter` only touches what is *behind* the element, so the panel's own text/children stay perfectly crisp.
- **Specular rim:** a diagonal white-to-transparent gradient is confined to a 1.4px border by painting two masks and compositing them with `exclude` (everything except the padding box is masked out). It lights the top-left and bottom-right edges — the tell-tale lit lip of real glass.
- **Flowing backdrop:** blurred colour blobs drift on slow `transform` keyframes and two bright streaks cross behind the glass, giving the refraction moving contrast to bend — that motion is what makes it read "liquid" rather than "frosted".

## Customization

- **Refraction strength:** `feDisplacementMap` `scale` — `~20` is a subtle lens, `~38` (demo) is elegant, `60+` gets watery (watch backdrop legibility). `baseFrequency` sets ripple scale: lower = broad swells, higher = fine chop.
- **Frost:** `blur(4px)` light → `blur(14px)` heavy milk-glass. Pair with `saturate(140–180%)`.
- **Rim:** raise the gradient's white stops for a crisper, more "Apple" edge; rotate the gradient angle to move where the light catches.
- **Palette / mood:** recolour the blobs and the accent gradients. Swapping the blob backdrop for a photo makes the refraction far more obvious (great for hero imagery).

## Accessibility & performance

- `backdrop-filter` **plus** an SVG displacement is GPU-heavy. Keep it to a few panels, and **never animate the filter itself** — animate cheap `transform`s on the *backdrop* instead (as the demo does).
- **Reduced motion:** stop the blob/streak/sheen animations, pin any animated value (e.g. give the gauge arc its final `stroke-dashoffset` as a base value, not just an animation endpoint), and `return` early in JS before attaching the pointer loop. The result is a fully static, still-beautiful glass panel.
- Keep text contrast over the *blurred* region; if content sits over light areas, deepen the panel tint. Provide the `@supports` solid-tint fallback shown above.
- Content legibility is safe by design: `backdrop-filter` distorts only the backdrop, never the panel's own children.

## Gotchas

- **`backdrop-filter: …​ url(#filter)` refraction is Chromium-only.** Safari (`-webkit-backdrop-filter`) and Firefox ignore the `url()` and fall back to plain blur — so keep the `-webkit-` line **without** the `url()`, and make sure blur + rim alone still look like glass (they do).
- **Needs the `-webkit-` prefix** or Safari/iOS shows no blur at all.
- **Nothing behind it = nothing happens.** The glass must overlap the colour backdrop; a glass panel painting its own solid background just looks like a translucent box.
- A stage with `overflow:hidden` + `border-radius` needs `isolation:isolate` so the blur samples only inside it. An ancestor `filter`/`transform` silently breaks the backdrop's containing block — keep the glass and its backdrop as **siblings**.
- The specular-rim mask needs **both** `-webkit-mask-composite:xor` and standard `mask-composite:exclude`.
- Give `feDisplacementMap` `color-interpolation-filters="sRGB"` (or colours wash out) and a filter region with negative `x`/`y` + `width/height > 100%` so displaced edges aren't clipped.
