08 / 20
All 20 in 20 Tailwind Liquid Glass Effects
- 01Tailwind Liquid Glass Card with Hover Refraction
- 02Tailwind Liquid Glass Button with Glossy Highlight
- 03Tailwind Liquid Glass Navigation Bar
- 04Tailwind Liquid Gooey Toggle Switch
- 05Tailwind Liquid Glass Modal Dialog
- 06Tailwind Liquid Glass Theme Switcher
- 07Tailwind Liquid Glass Input Form Fields
- 08Tailwind SVG Displacement Liquid Glass Background
- 09Tailwind Liquid Glass Dropdown Menu
- 10Tailwind Liquid Glass Pricing Table
- 11Tailwind Liquid Glass Tooltip Popover
- 12Tailwind Liquid Glass Audio Player UI
- 13Tailwind Liquid Glass Range Slider
- 14Tailwind Liquid Glass Toast Notification
- 15Tailwind Liquid Glass Sidebar Navigation
- 16Tailwind Liquid Glass Accordion
- 17Tailwind Liquid Glass Login Card
- 18Tailwind Liquid Glass Floating Action Button
- 19Tailwind Liquid Glass Loading Skeleton
- 20Tailwind Liquid Glass Multi-Step Form Wizard
Tailwind SVG Displacement Liquid Glass Background
The real thing: feTurbulence plus feDisplacementMap physically bending the image beneath the glass, with warp intensity following the cursor. Tailwind styles the panel, the stage and the layout; the filter stays SVG because displacement is geometry, not styling — and that division is exactly the point of the port.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section id="lgt-08-root" class="lgt-08 relative grid w-full min-h-svh place-items-center bg-lgt08-bg p-15 font-lgt08-sans" aria-label="Tailwind SVG displacement liquid glass background">
<svg id="lgt-08-svg" viewBox="0 0 600 380" width="600" height="380" preserveAspectRatio="xMidYMid slice" aria-hidden="true" class="pointer-events-none absolute col-start-1 row-start-1 h-auto w-[min(720px,94vw)] overflow-hidden rounded-[26px] saturate-120 shadow-[0_40px_80px_-34px_rgba(0,0,0,.8)]">
<defs>
<filter id="lgt-08-warp" x="-20%" y="-20%" width="140%" height="140%">
<feTurbulence id="lgt-08-turb" type="fractalNoise" baseFrequency="0.012 0.02" numOctaves="2" seed="7" result="noise"/>
<feDisplacementMap id="lgt-08-disp" in="SourceGraphic" in2="noise" scale="14" xChannelSelector="R" yChannelSelector="G"/>
</filter>
</defs>
<image href="https://picsum.photos/seed/lgwarp/600/380" x="0" y="0" width="600" height="380" preserveAspectRatio="xMidYMid slice" filter="url(#lgt-08-warp)"/>
</svg>
<div class="relative z-2 col-start-1 row-start-1 w-[min(360px,84vw)] rounded-[22px] border border-white/32 bg-white/10 p-7 text-white backdrop-blur-[6px] backdrop-saturate-150 shadow-[inset_0_1px_0_rgba(255,255,255,.45),0_20px_50px_-26px_rgba(0,0,0,.6)]">
<span class="inline-block rounded-full border border-white/35 bg-white/20 px-2.75 py-1.25 text-[11px] font-bold uppercase tracking-[0.1em]">SVG feDisplacementMap</span>
<h3 class="mt-3.5 mb-2 text-[28px] font-extrabold tracking-[-0.02em]">Real refraction</h3>
<p class="text-[15px] leading-relaxed text-white [text-shadow:0_1px_8px_rgba(0,0,0,.45)]">Move your cursor — the image bends through the glass. This is displacement, not blur.</p>
</div>
</section><section id="lgt-08-root" class="lgt-08 relative grid w-full min-h-svh place-items-center bg-lgt08-bg p-15 font-lgt08-sans" aria-label="Tailwind SVG displacement liquid glass background">
<svg id="lgt-08-svg" viewBox="0 0 600 380" width="600" height="380" preserveAspectRatio="xMidYMid slice" aria-hidden="true" class="pointer-events-none absolute col-start-1 row-start-1 h-auto w-[min(720px,94vw)] overflow-hidden rounded-[26px] saturate-120 shadow-[0_40px_80px_-34px_rgba(0,0,0,.8)]">
<defs>
<filter id="lgt-08-warp" x="-20%" y="-20%" width="140%" height="140%">
<feTurbulence id="lgt-08-turb" type="fractalNoise" baseFrequency="0.012 0.02" numOctaves="2" seed="7" result="noise"/>
<feDisplacementMap id="lgt-08-disp" in="SourceGraphic" in2="noise" scale="14" xChannelSelector="R" yChannelSelector="G"/>
</filter>
</defs>
<image href="https://picsum.photos/seed/lgwarp/600/380" x="0" y="0" width="600" height="380" preserveAspectRatio="xMidYMid slice" filter="url(#lgt-08-warp)"/>
</svg>
<div class="relative z-2 col-start-1 row-start-1 w-[min(360px,84vw)] rounded-[22px] border border-white/32 bg-white/10 p-7 text-white backdrop-blur-[6px] backdrop-saturate-150 shadow-[inset_0_1px_0_rgba(255,255,255,.45),0_20px_50px_-26px_rgba(0,0,0,.6)]">
<span class="inline-block rounded-full border border-white/35 bg-white/20 px-2.75 py-1.25 text-[11px] font-bold uppercase tracking-[0.1em]">SVG feDisplacementMap</span>
<h3 class="mt-3.5 mb-2 text-[28px] font-extrabold tracking-[-0.02em]">Real refraction</h3>
<p class="text-[15px] leading-relaxed text-white [text-shadow:0_1px_8px_rgba(0,0,0,.45)]">Move your cursor — the image bends through the glass. This is displacement, not blur.</p>
</div>
</section>@import "tailwindcss";
@theme {
--color-lgt08-bg: #0c0a16;
--font-lgt08-sans: 'Manrope', system-ui, sans-serif;
}@import "tailwindcss";
@theme {
--color-lgt08-bg: #0c0a16;
--font-lgt08-sans: 'Manrope', system-ui, sans-serif;
}(() => {
const root = document.querySelector('#lgt-08-root');
const disp = root.querySelector('#lgt-08-disp');
const turb = root.querySelector('#lgt-08-turb');
const svg = root.querySelector('#lgt-08-svg');
if (!disp) return;
const reduce = matchMedia('(prefers-reduced-motion: reduce)').matches;
const BASE = 14, MAX = 40;
let target = BASE, current = BASE, raf = null;
const tick = () => {
current += (target - current) * 0.12;
disp.setAttribute('scale', current.toFixed(2));
if (Math.abs(target - current) > 0.2) { raf = requestAnimationFrame(tick); }
else { disp.setAttribute('scale', target.toFixed(2)); raf = null; }
};
const kick = () => { if (!raf) raf = requestAnimationFrame(tick); };
root.addEventListener('pointermove', e => {
if (reduce) return;
const r = svg.getBoundingClientRect();
const dx = Math.abs((e.clientX - (r.left + r.width / 2)) / (r.width / 2));
const dy = Math.abs((e.clientY - (r.top + r.height / 2)) / (r.height / 2));
const t = Math.min(1, (dx + dy) / 2);
target = BASE + t * (MAX - BASE);
const f = 0.012 + t * 0.02;
turb.setAttribute('baseFrequency', f.toFixed(4) + ' ' + (f * 1.6).toFixed(4));
kick();
});
root.addEventListener('pointerleave', () => { target = BASE; kick(); });
})();(() => {
const root = document.querySelector('#lgt-08-root');
const disp = root.querySelector('#lgt-08-disp');
const turb = root.querySelector('#lgt-08-turb');
const svg = root.querySelector('#lgt-08-svg');
if (!disp) return;
const reduce = matchMedia('(prefers-reduced-motion: reduce)').matches;
const BASE = 14, MAX = 40;
let target = BASE, current = BASE, raf = null;
const tick = () => {
current += (target - current) * 0.12;
disp.setAttribute('scale', current.toFixed(2));
if (Math.abs(target - current) > 0.2) { raf = requestAnimationFrame(tick); }
else { disp.setAttribute('scale', target.toFixed(2)); raf = null; }
};
const kick = () => { if (!raf) raf = requestAnimationFrame(tick); };
root.addEventListener('pointermove', e => {
if (reduce) return;
const r = svg.getBoundingClientRect();
const dx = Math.abs((e.clientX - (r.left + r.width / 2)) / (r.width / 2));
const dy = Math.abs((e.clientY - (r.top + r.height / 2)) / (r.height / 2));
const t = Math.min(1, (dx + dy) / 2);
target = BASE + t * (MAX - BASE);
const f = 0.012 + t * 0.02;
turb.setAttribute('baseFrequency', f.toFixed(4) + ' ' + (f * 1.6).toFixed(4));
kick();
});
root.addEventListener('pointerleave', () => { target = BASE; kick(); });
})();Setup — do this once per project
Skip this if your project already runs Tailwind v4.
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>Compiles in the browser — perfect for a CodePen or a quick test, not for production.
npm install tailwindcss @tailwindcss/vite
npm install tailwindcss @tailwindcss/vite// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})Any v4 setup works — Vite, PostCSS or the CLI. Nothing in this demo is bundler-specific.
@import "tailwindcss";
@theme {
--color-lgt08-bg: #0c0a16;
--font-lgt08-sans: 'Manrope', system-ui, sans-serif;
}@import "tailwindcss";
@theme {
--color-lgt08-bg: #0c0a16;
--font-lgt08-sans: 'Manrope', system-ui, sans-serif;
}<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;600;800&display=swap">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;600;800&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- The filter stays SVG and the layout becomes utilities — a clean division: Tailwind owns the surface, SVG owns the physics.
grid place-items-centerwith both children oncol-start-1 row-start-1stacks the warped image and the panel without absolute positioning.- The panel's blur is deliberately low; heavy frost over a displaced image erases the very effect the demo is built to show.
- Filter ids carry the
lgt-08-namespace because SVG filter references are document-global and collisions fail silently. - The rAF easing loop decouples pointer frequency from paint frequency, which is what keeps a full-region filter INP-safe.
Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind SVG Displacement Liquid Glass Background
Source: https://codefronts.com/design-styles/tailwind-liquid-glass-effects/tailwind-svg-displacement-liquid-glass-background/
The real thing: feTurbulence plus feDisplacementMap physically bending the image beneath the glass, with warp intensity following the cursor. Tailwind styles the panel, the stage and the layout; the filter stays SVG because displacement is geometry, not styling — and that division is exactly the point of the port.
## HTML
```html
<section id="lgt-08-root" class="lgt-08 relative grid w-full min-h-svh place-items-center bg-lgt08-bg p-15 font-lgt08-sans" aria-label="Tailwind SVG displacement liquid glass background">
<svg id="lgt-08-svg" viewBox="0 0 600 380" width="600" height="380" preserveAspectRatio="xMidYMid slice" aria-hidden="true" class="pointer-events-none absolute col-start-1 row-start-1 h-auto w-[min(720px,94vw)] overflow-hidden rounded-[26px] saturate-120 shadow-[0_40px_80px_-34px_rgba(0,0,0,.8)]">
<defs>
<filter id="lgt-08-warp" x="-20%" y="-20%" width="140%" height="140%">
<feTurbulence id="lgt-08-turb" type="fractalNoise" baseFrequency="0.012 0.02" numOctaves="2" seed="7" result="noise"/>
<feDisplacementMap id="lgt-08-disp" in="SourceGraphic" in2="noise" scale="14" xChannelSelector="R" yChannelSelector="G"/>
</filter>
</defs>
<image href="https://picsum.photos/seed/lgwarp/600/380" x="0" y="0" width="600" height="380" preserveAspectRatio="xMidYMid slice" filter="url(#lgt-08-warp)"/>
</svg>
<div class="relative z-2 col-start-1 row-start-1 w-[min(360px,84vw)] rounded-[22px] border border-white/32 bg-white/10 p-7 text-white backdrop-blur-[6px] backdrop-saturate-150 shadow-[inset_0_1px_0_rgba(255,255,255,.45),0_20px_50px_-26px_rgba(0,0,0,.6)]">
<span class="inline-block rounded-full border border-white/35 bg-white/20 px-2.75 py-1.25 text-[11px] font-bold uppercase tracking-[0.1em]">SVG feDisplacementMap</span>
<h3 class="mt-3.5 mb-2 text-[28px] font-extrabold tracking-[-0.02em]">Real refraction</h3>
<p class="text-[15px] leading-relaxed text-white [text-shadow:0_1px_8px_rgba(0,0,0,.45)]">Move your cursor — the image bends through the glass. This is displacement, not blur.</p>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-lgt08-bg: #0c0a16;
--font-lgt08-sans: 'Manrope', system-ui, sans-serif;
}
```
## JavaScript
```js
(() => {
const root = document.querySelector('#lgt-08-root');
const disp = root.querySelector('#lgt-08-disp');
const turb = root.querySelector('#lgt-08-turb');
const svg = root.querySelector('#lgt-08-svg');
if (!disp) return;
const reduce = matchMedia('(prefers-reduced-motion: reduce)').matches;
const BASE = 14, MAX = 40;
let target = BASE, current = BASE, raf = null;
const tick = () => {
current += (target - current) * 0.12;
disp.setAttribute('scale', current.toFixed(2));
if (Math.abs(target - current) > 0.2) { raf = requestAnimationFrame(tick); }
else { disp.setAttribute('scale', target.toFixed(2)); raf = null; }
};
const kick = () => { if (!raf) raf = requestAnimationFrame(tick); };
root.addEventListener('pointermove', e => {
if (reduce) return;
const r = svg.getBoundingClientRect();
const dx = Math.abs((e.clientX - (r.left + r.width / 2)) / (r.width / 2));
const dy = Math.abs((e.clientY - (r.top + r.height / 2)) / (r.height / 2));
const t = Math.min(1, (dx + dy) / 2);
target = BASE + t * (MAX - BASE);
const f = 0.012 + t * 0.02;
turb.setAttribute('baseFrequency', f.toFixed(4) + ' ' + (f * 1.6).toFixed(4));
kick();
});
root.addEventListener('pointerleave', () => { target = BASE; kick(); });
})();
```Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind SVG Displacement Liquid Glass Background
Source: https://codefronts.com/design-styles/tailwind-liquid-glass-effects/tailwind-svg-displacement-liquid-glass-background/
The real thing: feTurbulence plus feDisplacementMap physically bending the image beneath the glass, with warp intensity following the cursor. Tailwind styles the panel, the stage and the layout; the filter stays SVG because displacement is geometry, not styling — and that division is exactly the point of the port.
## HTML
```html
<section id="lgt-08-root" class="lgt-08 relative grid w-full min-h-svh place-items-center bg-lgt08-bg p-15 font-lgt08-sans" aria-label="Tailwind SVG displacement liquid glass background">
<svg id="lgt-08-svg" viewBox="0 0 600 380" width="600" height="380" preserveAspectRatio="xMidYMid slice" aria-hidden="true" class="pointer-events-none absolute col-start-1 row-start-1 h-auto w-[min(720px,94vw)] overflow-hidden rounded-[26px] saturate-120 shadow-[0_40px_80px_-34px_rgba(0,0,0,.8)]">
<defs>
<filter id="lgt-08-warp" x="-20%" y="-20%" width="140%" height="140%">
<feTurbulence id="lgt-08-turb" type="fractalNoise" baseFrequency="0.012 0.02" numOctaves="2" seed="7" result="noise"/>
<feDisplacementMap id="lgt-08-disp" in="SourceGraphic" in2="noise" scale="14" xChannelSelector="R" yChannelSelector="G"/>
</filter>
</defs>
<image href="https://picsum.photos/seed/lgwarp/600/380" x="0" y="0" width="600" height="380" preserveAspectRatio="xMidYMid slice" filter="url(#lgt-08-warp)"/>
</svg>
<div class="relative z-2 col-start-1 row-start-1 w-[min(360px,84vw)] rounded-[22px] border border-white/32 bg-white/10 p-7 text-white backdrop-blur-[6px] backdrop-saturate-150 shadow-[inset_0_1px_0_rgba(255,255,255,.45),0_20px_50px_-26px_rgba(0,0,0,.6)]">
<span class="inline-block rounded-full border border-white/35 bg-white/20 px-2.75 py-1.25 text-[11px] font-bold uppercase tracking-[0.1em]">SVG feDisplacementMap</span>
<h3 class="mt-3.5 mb-2 text-[28px] font-extrabold tracking-[-0.02em]">Real refraction</h3>
<p class="text-[15px] leading-relaxed text-white [text-shadow:0_1px_8px_rgba(0,0,0,.45)]">Move your cursor — the image bends through the glass. This is displacement, not blur.</p>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-lgt08-bg: #0c0a16;
--font-lgt08-sans: 'Manrope', system-ui, sans-serif;
}
```
## JavaScript
```js
(() => {
const root = document.querySelector('#lgt-08-root');
const disp = root.querySelector('#lgt-08-disp');
const turb = root.querySelector('#lgt-08-turb');
const svg = root.querySelector('#lgt-08-svg');
if (!disp) return;
const reduce = matchMedia('(prefers-reduced-motion: reduce)').matches;
const BASE = 14, MAX = 40;
let target = BASE, current = BASE, raf = null;
const tick = () => {
current += (target - current) * 0.12;
disp.setAttribute('scale', current.toFixed(2));
if (Math.abs(target - current) > 0.2) { raf = requestAnimationFrame(tick); }
else { disp.setAttribute('scale', target.toFixed(2)); raf = null; }
};
const kick = () => { if (!raf) raf = requestAnimationFrame(tick); };
root.addEventListener('pointermove', e => {
if (reduce) return;
const r = svg.getBoundingClientRect();
const dx = Math.abs((e.clientX - (r.left + r.width / 2)) / (r.width / 2));
const dy = Math.abs((e.clientY - (r.top + r.height / 2)) / (r.height / 2));
const t = Math.min(1, (dx + dy) / 2);
target = BASE + t * (MAX - BASE);
const f = 0.012 + t * 0.02;
turb.setAttribute('baseFrequency', f.toFixed(4) + ' ' + (f * 1.6).toFixed(4));
kick();
});
root.addEventListener('pointerleave', () => { target = BASE; kick(); });
})();
```How this works
Displacement is not blur, and this is the demo that proves it. feTurbulence generates a smooth fractal noise field; feDisplacementMap reads that noise and pushes each pixel of the image sideways and vertically by scale. The result bends edges the way real rippled glass does — something no amount of backdrop-blur can imitate.
Tailwind's job here is everything around the filter. The stage, the panel, the rim, the type. The filter's job is the refraction. Keeping that boundary clean is why the port stays readable:
<!-- utilities: the glass panel -->
class="bg-white/10 border border-white/32
backdrop-blur-[6px] backdrop-saturate-150"
<!-- SVG: the physics -->
<feDisplacementMap scale="14" xChannelSelector="R" yChannelSelector="G"/>The pointer handler eases rather than tracks. Each pointermove sets a target; a requestAnimationFrame loop moves the current value 12% of the way there per frame and stops when it arrives. Pointer events can fire faster than frames, so this keeps filter re-evaluation bounded and protects INP.
The panel is barely frosted on purpose. A heavy blur over a displaced image erases the displacement. 6px of blur plus a strong rim reads as glass while leaving the warp legible.
Make it yours
- Change the ripple's character with
baseFrequency: low values give big lazy waves, high values give tight ripples.numOctaves="2"is the sweet spot for cost versus richness. - Set the resting and peak warp with the
BASEandMAXconstants; above roughly 40 the image stops reading as an image. - Add an SVG
<animate>onbaseFrequencyfor a glass that flows continuously even without a pointer. - Swap the image seed for your own art — keep
preserveAspectRatio="xMidYMid slice"so it fills without distorting. - Raise the panel's blur if you want a more conventional frosted card, accepting that you are trading away the refraction you built.
Gotchas — read before shipping
- Displacement is GPU work but not free. Cap
scaleand throttle updates with rAF, or a large filtered region will cost you INP on mid-range hardware. - Filter the image, never the panel. Applying the warp to a container that holds text turns your UI into liquid nonsense.
- SVG filter ids are document-global. Two un-namespaced
#warpfilters resolve to the first and the second element silently gets the wrong distortion. - Setting
scaleviasetAttributeis correct here — it is an SVG attribute, not a CSS property, and no Tailwind utility can reach it. - Keep a sensible resting state. If the whole effect only exists on
pointermove, touch users see a flat image. - Under reduced motion, stop updating the filter entirely rather than easing more slowly — rippling geometry is precisely what the query is asking you to stop.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
SVG filters are universally supported and long predate Tailwind; the v4 sheet sets the floor. The cursor-reactive warp is progressive enhancement — with no JS the panel already floats over a permanently refracted image.