Liquid Glass Refraction
Trying to reproduce Apple's liquid glass effect using only SVG filters. Documenting what works, what breaks, and why it's harder than it looks.
Apple’s liquid glass is convincing because it does three things at once: it refracts the content behind it (the background bends through the shape like a lens), it has a caustic edge (a bright ring where the curved surface bends the most light), and a specular highlight that moves with the light source. Together those make your eye read it as a physical object.
The question is whether you can fake all three with SVG filters and CSS — no canvas, no WebGL, no shaders.
The naive approach
The obvious starting point is feDisplacementMap. Feed it some noise, apply to a pill shape, done — right?
<filter id="glass-base" x="-20%" y="-20%" width="140%" height="140%"
color-interpolation-filters="sRGB">
<feTurbulence type="fractalNoise" baseFrequency="0.015 0.015"
numOctaves="3" seed="2" result="noise" />
<feColorMatrix in="noise" type="matrix"
values="0 0 0 0 0.5 0 0 0 0 0.5 0 0 0 0 1 0 0 0 0 1"
result="normal-map" />
<feComposite in="normal-map" in2="SourceGraphic"
operator="in" result="masked-normal" />
<feDisplacementMap in="SourceGraphic" in2="masked-normal"
scale="30" xChannelSelector="R" yChannelSelector="G" />
</filter>
It displaces. But compare it to the real thing and it falls apart immediately.
What’s wrong with it
The normal map is noise, not a lens. Real glass has a smooth, continuous surface — the refraction is strongest at the edges and smoothly fades toward the center. feTurbulence gives you random noise. The displacement looks jittery and organic, not like light bending through curved glass.
The caustic ring is missing. In real liquid glass, you see a bright edge where the surface curves most sharply — that’s the caustic. SVG has feSpecularLighting which can fake a highlight, but getting it to trace the edge of the shape specifically requires a proper edge-detected normal map. Right now the specular just picks a random bump from the noise.
backdrop-filter and filter can’t coexist on the same element. The filter property creates a new compositing layer, which means backdrop-filter on a child element has nothing to blur against — it just blurs the layer itself. To get actual frosted glass behind the displaced content you need to compose them separately, which feDisplacementMap can’t do natively.
Safari compositing bug. On Safari, applying an SVG filter on a parent while using backdrop-filter on a child breaks entirely — the filter runs before the backdrop is resolved.
What would actually work
The displacement needs a radial gradient normal map instead of noise — a shape that’s bright at the edges and dark in the center, which drives the displacement outward like a real lens. That requires either feImage with a pre-rendered gradient or feFlood + feComposite tricks, neither of which is clean.
The caustic ring needs edge detection on the shape — feMorphology to erode the shape, subtract from the original to get just the edge, then use that as the specular input.
The backdrop-filter conflict means the blur layer and the displacement layer need to be separate elements composited together with mix-blend-mode, not nested.
That’s what this experiment is working toward. None of it is solved yet.