Liquid glass
Also called Apple Liquid Glass or iOS 26 Liquid Glass
Apple’s translucent interface material from 2025 that bends, reflects, and tints the content behind it, with bright highlights along its edges.
import {
Fragment,
useEffect,
useId,
useRef,
useState,
type CSSProperties,
type ReactNode
} from 'react';
// Liquid glass the way liquid-glass-react (MIT, by Max Rovensky) builds it:
// the backdrop is frosted and bent by an SVG displacement filter that moves
// red, green, and blue by slightly different amounts, with two rims that
// catch the light, a soft shadow, a hover sheen, and an elastic lean toward
// the pointer. The bend renders in Chromium; other browsers keep the rest.
const BEND = 70; // how far the map can shift the backdrop, in px
const ELASTICITY = 0.15; // 0 is rigid
const FROST = 'blur(6px) saturate(140%)';
// The displacement map: red holds the x shift (an S-curve across), blue the
// y shift (a ramp down), and 128 means no shift.
const stops = (values: number[], color: (value: number) => string) =>
values
.map((v, i) => `<stop offset="${i / 8}" stop-color="${color(v)}"/>`)
.join('');
const MAP = `data:image/svg+xml,${encodeURIComponent(
`<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256">
<linearGradient id="x">${stops([250, 234, 214, 178, 124, 72, 33, 15, 1], (v) => `rgb(${v},0,0)`)}</linearGradient>
<linearGradient id="y" x2="0" y2="1">${stops([255, 248, 230, 190, 131, 76, 36, 14, 0], (v) => `rgb(0,0,${v})`)}</linearGradient>
<rect width="256" height="256" fill="url(#x)"/>
<rect width="256" height="256" fill="url(#y)" style="mix-blend-mode:screen"/>
</svg>`
)}`;
// Each color channel is displaced a little further than the last.
const CHANNELS = [
{ scale: -BEND, keep: '1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0' },
{ scale: -BEND * 1.1, keep: '0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0' },
{ scale: -BEND * 1.2, keep: '0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0' }
];
function GlassFilter({ id }: { id: string }) {
// Sized like the glass, since the map's percentages resolve against it.
return (
<svg aria-hidden='true' className='pointer-events-none absolute size-full'>
<filter
id={id}
x='-35%'
y='-35%'
width='170%'
height='170%'
colorInterpolationFilters='sRGB'
>
<feImage
href={MAP}
x='0'
y='0'
width='100%'
height='100%'
preserveAspectRatio='xMidYMid slice'
result='map'
/>
{CHANNELS.map(({ scale, keep }, index) => (
<Fragment key={index}>
<feDisplacementMap
in='SourceGraphic'
in2='map'
scale={scale}
xChannelSelector='R'
yChannelSelector='B'
/>
<feColorMatrix type='matrix' values={keep} result={`c${index}`} />
</Fragment>
))}
<feBlend in='c0' in2='c1' mode='screen' />
<feBlend in2='c2' mode='screen' />
<feGaussianBlur stdDeviation='0.3' />
</filter>
</svg>
);
}
/** Wraps any content in liquid glass. Position it with `className`. */
export function LiquidGlass({
className,
children
}: {
className: string;
children: ReactNode;
}) {
const filterId = `glass${useId().replace(/[^\w-]/g, '')}`;
const ref = useRef<HTMLDivElement>(null);
const [lean, setLean] = useState({ transform: 'none', x: 0, y: 0 });
const [hovered, setHovered] = useState(false);
const [pressed, setPressed] = useState(false);
// Within 200px, lean and stretch toward the pointer; the rims' light
// turns with it everywhere.
useEffect(() => {
const follow = (event: PointerEvent) => {
const element = ref.current;
if (!element) return;
const rect = element.getBoundingClientRect();
const { offsetWidth: width, offsetHeight: height } = element;
const dx = event.clientX - rect.left - rect.width / 2;
const dy = event.clientY - rect.top - rect.height / 2;
const edge = Math.hypot(
Math.max(0, Math.abs(dx) - width / 2),
Math.max(0, Math.abs(dy) - height / 2)
);
const near = Math.max(0, 1 - edge / 200);
const distance = Math.hypot(dx, dy) || 1;
const stretch = Math.min(distance / 300, 1) * ELASTICITY * near;
const along = (a: number, b: number) =>
Math.max(0.8, 1 + (a * 0.3 - b * 0.15) * stretch);
const [ax, ay] = [Math.abs(dx) / distance, Math.abs(dy) / distance];
const shift = ELASTICITY * 0.1 * near;
setLean({
transform: `translate(${dx * shift}px, ${dy * shift}px) scale(${along(ax, ay)}, ${along(ay, ax)})`,
x: (dx / width) * 100,
y: (dy / height) * 100
});
};
window.addEventListener('pointermove', follow);
return () => window.removeEventListener('pointermove', follow);
}, []);
// The library screens and overlays these rims onto the glass. Blend modes
// here would make Chrome isolate the glass and leave its backdrop-filter
// nothing to frost, so they are plain white, dimmed to look the same.
const rim = (low: number, high: number): CSSProperties => ({
padding: 1.5,
mask: 'linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0)',
boxShadow:
'inset 0 0 0 0.5px rgb(255 255 255 / 0.5), inset 0 1px 3px rgb(255 255 255 / 0.25), 0 1px 4px rgb(0 0 0 / 0.35)',
background: `linear-gradient(${135 + lean.x * 1.2}deg, transparent, rgb(255 255 255 / ${low + Math.abs(lean.x) * 0.008}) ${Math.max(10, 33 + lean.y * 0.3)}%, rgb(255 255 255 / ${high + Math.abs(lean.x) * 0.012}) ${Math.min(90, 66 + lean.y * 0.4)}%, transparent)`
});
const sheen = pressed ? 0.2 : hovered ? 0.1 : 0;
return (
<div
ref={ref}
className={`rounded-full transition-transform duration-200 ease-out ${className}`}
style={{ transform: lean.transform }}
onPointerEnter={() => setHovered(true)}
onPointerLeave={() => {
setHovered(false);
setPressed(false);
}}
onPointerDown={() => setPressed(true)}
onPointerUp={() => setPressed(false)}
>
<div className='relative overflow-hidden rounded-full shadow-[0_12px_40px_rgb(0_0_0/0.25)]'>
<GlassFilter id={filterId} />
<span
className='absolute inset-0'
style={{ backdropFilter: FROST, filter: `url(#${filterId})` }}
/>
<div className='relative text-white [text-shadow:0_2px_12px_rgb(0_0_0/0.4)]'>
{children}
</div>
</div>
<span
aria-hidden='true'
className='pointer-events-none absolute inset-0 rounded-full opacity-20'
style={rim(0.12, 0.4)}
/>
<span
aria-hidden='true'
className='pointer-events-none absolute inset-0 rounded-full'
style={rim(0.12, 0.24)}
/>
<span
aria-hidden='true'
className='pointer-events-none absolute inset-0 rounded-full bg-[radial-gradient(circle_at_50%_0%,white,transparent)] transition-opacity duration-200'
style={{ opacity: sheen }}
/>
</div>
);
}
const TABS = ['Home', 'New', 'Radio', 'Library'];
export function GlassTabBar() {
const [active, setActive] = useState('Home');
return (
<LiquidGlass className='fixed bottom-6 left-1/2 -translate-x-1/2'>
<nav aria-label='Main' className='flex gap-1 p-1.5'>
{TABS.map((tab) => (
<button
key={tab}
type='button'
aria-current={active === tab ? 'page' : undefined}
onClick={() => setActive(tab)}
className='rounded-full px-4 py-2 text-sm font-medium transition-colors aria-[current=page]:bg-white/20 aria-[current=page]:text-red-400'
>
{tab}
</button>
))}
</nav>
</LiquidGlass>
);
}Make the bottom tab bar look like Apple’s Liquid Glass, built the way the open-source liquid-glass-react library does it. Frost the backdrop with a 6px blur and 140% saturation, and bend it near the edges with an SVG displacement filter (scale 70) that shifts red, green, and blue slightly apart. Add two thin rims whose light turns toward the pointer, a soft shadow, a hover sheen, and a slight elastic lean toward the pointer. Keep the rims in normal blending: blend modes next to the glass stop Chrome from frosting the backdrop. Keep the labels white, and put the selected tab on a 20% white pill. The bending only renders in Chromium, so check that Safari and Firefox still look right with just the frost and rims.
Paste into Layerwise or any AI coding tool.
What is Liquid glass?
Liquid Glass is the design material Apple introduced at WWDC in June 2025 for iOS 26, macOS Tahoe 26, and its other platforms. Tab bars, toolbars, and controls look like pieces of curved glass: content behind them bends near the edges, highlights catch the light as things move, and the tint adapts to what is underneath. Controls can also stretch and merge like drops of liquid as they change.
On the web, convincing versions stack several layers. The demo above and the example below are built like the open-source liquid-glass-react library: they frost the backdrop with backdrop-filter, bend it with an SVG feDisplacementMap filter that shifts red, green, and blue by slightly different amounts for faint color fringes, draw two 1.5px rims whose gradient turns toward the pointer, and add a soft shadow and an elastic lean. Only Chromium-based browsers run SVG filters on a backdrop, so Safari and Firefox get the frost and rims without the bending.
Use it the way Apple does: for floating navigation and controls that sit above content, rather than for whole cards of text. Like any glass effect it needs a contrast check, and some people turn on reduced transparency in their system settings, so respect prefers-reduced-transparency where browsers support it and fall back to a more opaque fill.
Often confused with
- Glassmorphism
- Glassmorphism is a heavily frosted blur with a light border. Liquid glass stays clearer and adds bending light and moving highlights at the edges.
Related terms
All termsGlassmorphism
A visual style where panels look like frosted glass: translucent, blurred backgrounds with a thin light border over colorful shapes.
Blur
A softening effect made with CSS filter: blur() on an element itself, or backdrop-filter: blur() on whatever shows through it from behind.
Skeuomorphism
A design style where digital controls imitate real objects and materials, such as metal knobs, leather, paper, and physical switches.
Tabs
A row of labels that switches between panels of related content in the same spot, showing one panel at a time.
Build it with Layerwise
Describe your app, watch it run in a live preview, and publish it with a database, storage, and sign-in built in.

















