13 / 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 Liquid Glass Range Slider
A heavy glass lens sliding across a numeric scale, magnifying the value beneath it. The native range input stays on top and fully transparent so keyboard, pointer and screen-reader behaviour are untouched; every glass layer under it is decoration positioned from a single --v custom property.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section id="lgt-13-root" class="lgt-13 grid w-full min-h-svh place-items-center px-6 py-15 font-lgt13-sans [background:radial-gradient(circle_at_30%_30%,#1c3050,#0d1424)]" aria-label="Tailwind liquid glass range slider">
<div id="lgt-13-wrap" style="--v:40" class="group relative h-30 w-[min(440px,90vw)]">
<div aria-hidden="true" class="pointer-events-none absolute inset-x-0 top-8.5 flex justify-between text-[26px] font-extrabold text-white/14">
<span>0</span><span>25</span><span>50</span><span>75</span><span>100</span>
</div>
<div aria-hidden="true" class="pointer-events-none absolute inset-x-0 top-13.5 h-2.5 overflow-hidden rounded-full border border-white/20 bg-white/10 backdrop-blur-[6px] shadow-[inset_0_2px_6px_rgba(0,0,0,.5)]">
<div class="absolute inset-y-0 left-0 [width:calc(var(--v)*1%)] bg-linear-to-r from-lgt13-accent/60 to-lgt13-accent shadow-[0_0_16px_var(--color-lgt13-accent)]"></div>
</div>
<div aria-hidden="true" class="pointer-events-none absolute top-[59px] grid size-14 -translate-x-1/2 -translate-y-1/2 place-items-center rounded-full border border-white/60 bg-white/14 backdrop-blur-[3px] backdrop-saturate-160 backdrop-brightness-110 shadow-[inset_0_2px_6px_rgba(255,255,255,.5),inset_0_-6px_12px_rgba(0,0,0,.35),0_10px_24px_-8px_rgba(0,0,0,.6)] [left:calc(var(--v)*1%)] transition-shadow duration-200 group-has-[:focus-visible]:shadow-[0_0_0_3px_#fff,inset_0_2px_6px_rgba(255,255,255,.5)] motion-reduce:transition-none">
<span id="lgt-13-mag" class="scale-115 text-2xl font-black text-white [text-shadow:0_1px_4px_rgba(0,0,0,.5)]">40</span>
</div>
<input id="lgt-13-input" type="range" min="0" max="100" value="40" aria-label="Quality" class="absolute -left-2 top-11 h-10 w-[calc(100%+16px)] cursor-grab appearance-none bg-transparent opacity-0 active:cursor-grabbing">
</div>
</section><section id="lgt-13-root" class="lgt-13 grid w-full min-h-svh place-items-center px-6 py-15 font-lgt13-sans [background:radial-gradient(circle_at_30%_30%,#1c3050,#0d1424)]" aria-label="Tailwind liquid glass range slider">
<div id="lgt-13-wrap" style="--v:40" class="group relative h-30 w-[min(440px,90vw)]">
<div aria-hidden="true" class="pointer-events-none absolute inset-x-0 top-8.5 flex justify-between text-[26px] font-extrabold text-white/14">
<span>0</span><span>25</span><span>50</span><span>75</span><span>100</span>
</div>
<div aria-hidden="true" class="pointer-events-none absolute inset-x-0 top-13.5 h-2.5 overflow-hidden rounded-full border border-white/20 bg-white/10 backdrop-blur-[6px] shadow-[inset_0_2px_6px_rgba(0,0,0,.5)]">
<div class="absolute inset-y-0 left-0 [width:calc(var(--v)*1%)] bg-linear-to-r from-lgt13-accent/60 to-lgt13-accent shadow-[0_0_16px_var(--color-lgt13-accent)]"></div>
</div>
<div aria-hidden="true" class="pointer-events-none absolute top-[59px] grid size-14 -translate-x-1/2 -translate-y-1/2 place-items-center rounded-full border border-white/60 bg-white/14 backdrop-blur-[3px] backdrop-saturate-160 backdrop-brightness-110 shadow-[inset_0_2px_6px_rgba(255,255,255,.5),inset_0_-6px_12px_rgba(0,0,0,.35),0_10px_24px_-8px_rgba(0,0,0,.6)] [left:calc(var(--v)*1%)] transition-shadow duration-200 group-has-[:focus-visible]:shadow-[0_0_0_3px_#fff,inset_0_2px_6px_rgba(255,255,255,.5)] motion-reduce:transition-none">
<span id="lgt-13-mag" class="scale-115 text-2xl font-black text-white [text-shadow:0_1px_4px_rgba(0,0,0,.5)]">40</span>
</div>
<input id="lgt-13-input" type="range" min="0" max="100" value="40" aria-label="Quality" class="absolute -left-2 top-11 h-10 w-[calc(100%+16px)] cursor-grab appearance-none bg-transparent opacity-0 active:cursor-grabbing">
</div>
</section>@import "tailwindcss";
@theme {
--color-lgt13-accent: #4fd1e0;
--color-lgt13-accent: oklch(0.7 0.17 200);
--font-lgt13-sans: 'Manrope', system-ui, sans-serif;
}
/* The invisible input still needs a large hit area on both engines. */
.lgt-13 input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 56px;
height: 56px;
}
.lgt-13 input[type="range"]::-moz-range-thumb {
width: 56px;
height: 56px;
border: none;
background: transparent;
}@import "tailwindcss";
@theme {
--color-lgt13-accent: #4fd1e0;
--color-lgt13-accent: oklch(0.7 0.17 200);
--font-lgt13-sans: 'Manrope', system-ui, sans-serif;
}
/* The invisible input still needs a large hit area on both engines. */
.lgt-13 input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 56px;
height: 56px;
}
.lgt-13 input[type="range"]::-moz-range-thumb {
width: 56px;
height: 56px;
border: none;
background: transparent;
}(() => {
const root = document.querySelector('#lgt-13-root');
const input = root.querySelector('#lgt-13-input');
const wrap = root.querySelector('#lgt-13-wrap');
const mag = root.querySelector('#lgt-13-mag');
const sync = () => { wrap.style.setProperty('--v', input.value); mag.textContent = input.value; };
input.addEventListener('input', sync);
sync();
})();(() => {
const root = document.querySelector('#lgt-13-root');
const input = root.querySelector('#lgt-13-input');
const wrap = root.querySelector('#lgt-13-wrap');
const mag = root.querySelector('#lgt-13-mag');
const sync = () => { wrap.style.setProperty('--v', input.value); mag.textContent = input.value; };
input.addEventListener('input', sync);
sync();
})();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-lgt13-accent: #4fd1e0;
--color-lgt13-accent: oklch(0.7 0.17 200);
--font-lgt13-sans: 'Manrope', system-ui, sans-serif;
}
/* The invisible input still needs a large hit area on both engines. */
.lgt-13 input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 56px;
height: 56px;
}
.lgt-13 input[type="range"]::-moz-range-thumb {
width: 56px;
height: 56px;
border: none;
background: transparent;
}@import "tailwindcss";
@theme {
--color-lgt13-accent: #4fd1e0;
--color-lgt13-accent: oklch(0.7 0.17 200);
--font-lgt13-sans: 'Manrope', system-ui, sans-serif;
}
/* The invisible input still needs a large hit area on both engines. */
.lgt-13 input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 56px;
height: 56px;
}
.lgt-13 input[type="range"]::-moz-range-thumb {
width: 56px;
height: 56px;
border: none;
background: transparent;
}<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 native input stays on top at
opacity-0, so keyboard, touch, drag and value announcements are the browser's job, not the script's. group-has-[:focus-visible]:rings the visible lens when the invisible input takes focus — this replaces the CSS version's JS focus and blur handlers entirely.- One
--vvariable positions the lens and sizes the fill through arbitrarycalc()utilities. - The script is four lines: it syncs a variable and a label, and nothing else.
- The oversized vendor thumb rules stay in CSS because they exist purely to give the transparent input a 56px drag target.
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 Liquid Glass Range Slider
Source: https://codefronts.com/design-styles/tailwind-liquid-glass-effects/tailwind-liquid-glass-range-slider/
A heavy glass lens sliding across a numeric scale, magnifying the value beneath it. The native range input stays on top and fully transparent so keyboard, pointer and screen-reader behaviour are untouched; every glass layer under it is decoration positioned from a single --v custom property.
## HTML
```html
<section id="lgt-13-root" class="lgt-13 grid w-full min-h-svh place-items-center px-6 py-15 font-lgt13-sans [background:radial-gradient(circle_at_30%_30%,#1c3050,#0d1424)]" aria-label="Tailwind liquid glass range slider">
<div id="lgt-13-wrap" style="--v:40" class="group relative h-30 w-[min(440px,90vw)]">
<div aria-hidden="true" class="pointer-events-none absolute inset-x-0 top-8.5 flex justify-between text-[26px] font-extrabold text-white/14">
<span>0</span><span>25</span><span>50</span><span>75</span><span>100</span>
</div>
<div aria-hidden="true" class="pointer-events-none absolute inset-x-0 top-13.5 h-2.5 overflow-hidden rounded-full border border-white/20 bg-white/10 backdrop-blur-[6px] shadow-[inset_0_2px_6px_rgba(0,0,0,.5)]">
<div class="absolute inset-y-0 left-0 [width:calc(var(--v)*1%)] bg-linear-to-r from-lgt13-accent/60 to-lgt13-accent shadow-[0_0_16px_var(--color-lgt13-accent)]"></div>
</div>
<div aria-hidden="true" class="pointer-events-none absolute top-[59px] grid size-14 -translate-x-1/2 -translate-y-1/2 place-items-center rounded-full border border-white/60 bg-white/14 backdrop-blur-[3px] backdrop-saturate-160 backdrop-brightness-110 shadow-[inset_0_2px_6px_rgba(255,255,255,.5),inset_0_-6px_12px_rgba(0,0,0,.35),0_10px_24px_-8px_rgba(0,0,0,.6)] [left:calc(var(--v)*1%)] transition-shadow duration-200 group-has-[:focus-visible]:shadow-[0_0_0_3px_#fff,inset_0_2px_6px_rgba(255,255,255,.5)] motion-reduce:transition-none">
<span id="lgt-13-mag" class="scale-115 text-2xl font-black text-white [text-shadow:0_1px_4px_rgba(0,0,0,.5)]">40</span>
</div>
<input id="lgt-13-input" type="range" min="0" max="100" value="40" aria-label="Quality" class="absolute -left-2 top-11 h-10 w-[calc(100%+16px)] cursor-grab appearance-none bg-transparent opacity-0 active:cursor-grabbing">
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-lgt13-accent: #4fd1e0;
--color-lgt13-accent: oklch(0.7 0.17 200);
--font-lgt13-sans: 'Manrope', system-ui, sans-serif;
}
/* The invisible input still needs a large hit area on both engines. */
.lgt-13 input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 56px;
height: 56px;
}
.lgt-13 input[type="range"]::-moz-range-thumb {
width: 56px;
height: 56px;
border: none;
background: transparent;
}
```
## JavaScript
```js
(() => {
const root = document.querySelector('#lgt-13-root');
const input = root.querySelector('#lgt-13-input');
const wrap = root.querySelector('#lgt-13-wrap');
const mag = root.querySelector('#lgt-13-mag');
const sync = () => { wrap.style.setProperty('--v', input.value); mag.textContent = input.value; };
input.addEventListener('input', sync);
sync();
})();
```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 Liquid Glass Range Slider
Source: https://codefronts.com/design-styles/tailwind-liquid-glass-effects/tailwind-liquid-glass-range-slider/
A heavy glass lens sliding across a numeric scale, magnifying the value beneath it. The native range input stays on top and fully transparent so keyboard, pointer and screen-reader behaviour are untouched; every glass layer under it is decoration positioned from a single --v custom property.
## HTML
```html
<section id="lgt-13-root" class="lgt-13 grid w-full min-h-svh place-items-center px-6 py-15 font-lgt13-sans [background:radial-gradient(circle_at_30%_30%,#1c3050,#0d1424)]" aria-label="Tailwind liquid glass range slider">
<div id="lgt-13-wrap" style="--v:40" class="group relative h-30 w-[min(440px,90vw)]">
<div aria-hidden="true" class="pointer-events-none absolute inset-x-0 top-8.5 flex justify-between text-[26px] font-extrabold text-white/14">
<span>0</span><span>25</span><span>50</span><span>75</span><span>100</span>
</div>
<div aria-hidden="true" class="pointer-events-none absolute inset-x-0 top-13.5 h-2.5 overflow-hidden rounded-full border border-white/20 bg-white/10 backdrop-blur-[6px] shadow-[inset_0_2px_6px_rgba(0,0,0,.5)]">
<div class="absolute inset-y-0 left-0 [width:calc(var(--v)*1%)] bg-linear-to-r from-lgt13-accent/60 to-lgt13-accent shadow-[0_0_16px_var(--color-lgt13-accent)]"></div>
</div>
<div aria-hidden="true" class="pointer-events-none absolute top-[59px] grid size-14 -translate-x-1/2 -translate-y-1/2 place-items-center rounded-full border border-white/60 bg-white/14 backdrop-blur-[3px] backdrop-saturate-160 backdrop-brightness-110 shadow-[inset_0_2px_6px_rgba(255,255,255,.5),inset_0_-6px_12px_rgba(0,0,0,.35),0_10px_24px_-8px_rgba(0,0,0,.6)] [left:calc(var(--v)*1%)] transition-shadow duration-200 group-has-[:focus-visible]:shadow-[0_0_0_3px_#fff,inset_0_2px_6px_rgba(255,255,255,.5)] motion-reduce:transition-none">
<span id="lgt-13-mag" class="scale-115 text-2xl font-black text-white [text-shadow:0_1px_4px_rgba(0,0,0,.5)]">40</span>
</div>
<input id="lgt-13-input" type="range" min="0" max="100" value="40" aria-label="Quality" class="absolute -left-2 top-11 h-10 w-[calc(100%+16px)] cursor-grab appearance-none bg-transparent opacity-0 active:cursor-grabbing">
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-lgt13-accent: #4fd1e0;
--color-lgt13-accent: oklch(0.7 0.17 200);
--font-lgt13-sans: 'Manrope', system-ui, sans-serif;
}
/* The invisible input still needs a large hit area on both engines. */
.lgt-13 input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 56px;
height: 56px;
}
.lgt-13 input[type="range"]::-moz-range-thumb {
width: 56px;
height: 56px;
border: none;
background: transparent;
}
```
## JavaScript
```js
(() => {
const root = document.querySelector('#lgt-13-root');
const input = root.querySelector('#lgt-13-input');
const wrap = root.querySelector('#lgt-13-wrap');
const mag = root.querySelector('#lgt-13-mag');
const sync = () => { wrap.style.setProperty('--v', input.value); mag.textContent = input.value; };
input.addEventListener('input', sync);
sync();
})();
```How this works
The accessible control is the one you cannot see. A real range input sits on top at opacity-0. It handles dragging, arrow keys, Home/End, page keys and the screen reader's value announcements. Everything glass underneath is painted from its value:
<!-- fill --> class="[width:calc(var(--v)*1%)]"
<!-- lens --> class="[left:calc(var(--v)*1%)] -translate-x-1/2"has-[:focus-visible]: solves the focus-ring problem. The focusable element is invisible, so its own ring would be invisible too. The wrapper asks 'does something inside me have visible focus?' and rings the lens instead — one variant replacing the JS focus/blur handlers the CSS version needed.
Optical thickness is two inset shadows. A bright inset at the top edge and a darker one at the bottom, plus a light backdrop brightness boost. That combination is what separates a glass lens from a white circle.
The magnified number is honest. It shows the live value at 1.15 scale over the dimmed scale beneath, so the lens appears to be magnifying the ruler it slides along.
Make it yours
- Resize the lens and its zoom together — the disc diameter and the magnified number's scale should move as a pair or the illusion breaks.
- Add
step="5"for tick snapping; the lens follows automatically because it reads the value. - Recolour the fill and its glow from
--color-lgt13-accent. - Swap the magnified number for a value bubble above the lens if your scale is not numeric.
- Widen the track and the scale together; the percentages keep everything aligned at any width.
Gotchas — read before shipping
- Keep the native input on top and transparent. Hiding it and rebuilding drag with pointer events costs you keyboard support, screen-reader values and touch behaviour in one move.
opacity-0is right here;hiddenorsr-onlywould remove the pointer target that makes the slider draggable.- Every decorative layer needs
pointer-events-none, or the lens will swallow the drag it is supposed to represent. - backdrop-filter on a small circular element is more expensive than it looks — keep the blur at 3px, not 15px.
- Drive the lens from the input's value on every
inputevent; do not try to compute it from pointer coordinates. - The invisible input still needs a visible focus affordance. Without the
has-[:focus-visible]:ring, keyboard users are steering blind.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
The native range input gives full accessibility everywhere; :has() (Chrome 105+, Safari 15.4+, Firefox 121+) drives the focus ring and Tailwind v4's sheet sets the floor. Without backdrop-filter the lens is a solid glass ball that still shows position and value.