all writing

exploring liquid glass for the web

apple introduced liquid glass at WWDC 2025. i want to understand how it actually works and figure out if it's possible to recreate it on the web.

apple showed off liquid glass at WWDC 2025 and it immediately split the internet. half the people loved it, the other half couldn't read their phone anymore. both sides had a point.

i'm less interested in whether it's a good design choice and more interested in how it works. the effect is non-trivial. it goes well beyond glassmorphism with backdrop-filter: blur() into a multi-layered rendering pipeline that bends light through a simulated glass surface in real time.

i want to recreate it on the web.

what liquid glass actually is

apple calls it a "digital meta-material." it dynamically bends and shapes light like physical glass while moving fluidly like liquid. the WWDC session breaks it into several layers:

layer what it does
highlights respond to environmental lighting and device motion. lights move through 3D space.
shadows adjust opacity based on what's behind them. darker over text, lighter over solid backgrounds.
tinting continuously adapts. flips between light and dark based on the content underneath.
lensing the core innovation. background elements are warped through refraction instead of blurred.
illumination interactive feedback that spreads from your fingertip through the element.

the key difference from glassmorphism is lensing. previous apple materials scattered light (blur). liquid glass bends it. when you look at a button, the content behind it is displaced and warped as if you're looking through a curved piece of real glass. the displacement follows the shape's geometry, the surface curvature, and snell's law of refraction.

how apple implements it natively

AlexStrNik's ShatteredGlass reverse-engineered the macOS implementation. the layer hierarchy is an exploded stack of compositing passes, each handling a different aspect of the effect:

liquid-glass-layers
loading scene...

drag the slider to separate the layers. the native hierarchy from ShatteredGlass:

text
SwiftUI.SDFLayer (container)
├── CABackdropLayer ("@0")
│   ├── glassBackground CAFilter
│   │   (shadow, blur, refraction, face tint, bleed — all in one filter)
│   └── CASDFLayer ("@0") + CASDFElementLayer (defines the shape)
├── CASDFLayer ("@1") + CASDFGlassHighlightEffect (edge lighting, 45°)
│   └── vibrantColorMatrix filter
└── CASDFLayer ("@2") + CASDFGlassHighlightEffect (edge lighting, -135°)
    └── vibrantColorMatrix filter

the important bits:

  • SDF (Signed Distance Fields) define the shape geometry. SDFs replace meshes and paths with smooth distance-based calculations for normals, curvature, and surface detection.
  • a single glassBackground CAFilter on the CABackdropLayer handles refraction, blur, shadow, face tint, and bleed in one GPU pass. the visualization above splits these into separate layers for clarity.
  • two CASDFGlassHighlightEffect layers add directional edge highlights at opposite angles (45° and -135°), each with its own vibrantColorMatrix filter for tonal adjustment.

apple does this without ray tracing. it's closer to a screen-space refraction shader that uses the SDF normal to compute displacement. chromatic aberration (dispersion) emerges from sampling each color channel at slightly different refracted positions within the same shader. the GPU does all the heavy lifting.

the dday.it analysis confirms this: "it is the first time that a design language relies on some instructions of the graphic processor with shaders specifically developed to process a type of effect that is not at all trivial." they also note it responds to the accelerometer and gyroscope for light direction.

the math: snell's law and displacement

the core visual effect is refraction. when light passes from air into glass, it bends. the relationship is described by snell's law:

text
n1 * sin(theta1) = n2 * sin(theta2)

for a glass surface with refractive index ~1.5, light bends toward the normal when entering the glass. the amount of bending depends on the angle of incidence, which depends on the surface curvature at that point.

mws did an optically correct GLSL implementation from first principles. the approach:

  1. model the glass shape as a rounded rectangle with a curved bevel (half-sphere top + cylinder bottom)
  2. for each pixel, compute the distance to the glass edge using an SDF
  3. from the distance, compute the surface normal (the SDF gradient via dFdx/dFdy in GLSL)
  4. apply snell's law to compute the refraction vector
  5. the refracted ray hits the "floor" (the background content) at a displaced position
  6. sample the background at the displaced position instead of the original

the displacement is always inward for convex surfaces (which is what apple uses). this means the background content inside the glass shape appears slightly magnified at the center and displaced at the edges.

kube.io explored four surface functions:

surface shape effect
convex circle spherical dome sharp refraction edges, magnification in center
convex squircle apple's preferred shape smoother transitions, optically thinner bezel
concave bowl-like depression rays diverge outward (not used by apple)
lip convex rim + concave center used for switches and toggles

apple uses the squircle profile. it's smoother than a circular arc and keeps refraction gradients consistent even when stretched into rectangles.

web implementation approaches

this is where it gets interesting. there are several ways to approach liquid glass on the web, each with real tradeoffs.

approach 1: SVG displacement maps

kube.io built the most technically rigorous CSS/SVG implementation. the idea:

  1. pre-calculate the refraction displacement for a range of distances from the border
  2. encode the displacement vectors into an SVG image (red channel = X displacement, green = Y)
  3. use <feDisplacementMap> in an SVG filter to apply the refraction
  4. apply the filter via CSS backdrop-filter: url(#filter-id)
  5. add a specular highlight overlay for edge lighting

the catch is that backdrop-filter with SVG filters only works in Chrome. safari and firefox don't support this combination. the displacement map is also limited to 8-bit channels (256 values per axis), capping the precision, and any shape/size change requires rebuilding the entire displacement map.

approach 2: Three.js / WebGL

specy took a different route:

  1. use html2canvas to screenshot the page (the "paint layer")
  2. create a Three.js scene overlaid on the page with position: fixed
  3. place the screenshot as a texture behind a 3D glass object
  4. move the texture to match scroll position so it stays aligned with the real content
  5. the glass object refracts the texture using Three.js materials

they published a React library for this.

OverShifted built a standalone OpenGL shader that uses SDF-based refraction with blur, noise, and glow. the approach works but the resource usage is heavy.

approach 3: pure shader (shadertoy / raw WebGL)

mws's shadertoy implementation is the most physically accurate. it computes refraction from first principles using the SDF normal and GLSL's built-in refract() function. it adds reflection and shadow (neither physically correct, but visually convincing).

the problem mws notes: curvature discontinuity at the bevel-to-flat transition creates sharp visual artifacts. apple avoids this through careful surface function design. "apple has an extreme hate for curvature discontinuity."

the web's fundamental limitation

specy put it plainly: we don't have access to the paint layer. in native iOS, the GPU has direct access to the rendered content behind any element. on the web, CSS and JavaScript cannot read the pixels behind an element unless you hack around it with screenshots.

SVG filters are the exception. <feDisplacementMap> with SourceGraphic gets you access to the paint layer, but only within the constraints of the SVG filter pipeline. custom shaders and arbitrary per-pixel operations are off the table. you get displacement and blending and that's about it.

the html2canvas approach works as a workaround for demos, but falls short for production UI. it's expensive, async, and drifts out of sync with the real page.

the dday.it article goes further: "there is no Liquid Glass for the web, and it is not possible in any way to replicate the same effect using CSS."

arguments for doing this anyway

  • the effect is achievable in constrained contexts (fixed overlays, hero sections, interactive demos)
  • Three.js + React Three Fiber gives us full shader control for specific elements
  • a partial recreation that nails the core refraction is more interesting than a perfect backdrop-filter: blur()
  • WFD 17 now supports inline Three.js scenes, so interactive demonstrations are possible
  • it's a good exercise in understanding optics, SDFs, and GPU rendering

arguments against

  • the full liquid glass experience (adaptive tint, shadow response, motion feedback) requires paint layer access we don't have
  • WebGL overlays are expensive and don't compose well with regular DOM elements
  • the accessibility concerns are real. liquid glass has contrast issues even on native iOS where apple controls the rendering pipeline
  • browser support for the SVG filter approach is Chrome-only
  • the performance cost of a WebGL overlay for UI elements is significant compared to native GPU compositing

where i'm leaning

i want to build a focused recreation scoped to specific use cases. the plan:

  1. build interactive Three.js visualizations that demonstrate each concept (refraction, SDF normals, displacement maps, surface functions)
  2. create a standalone glass effect component using a custom shader instead of MeshTransmissionMaterial
  3. keep it scoped to specific elements (hero sections, interactive demos) and leave general UI alone

the interactive visualizations are the more interesting output. a ray diagram you can drag. a surface function you can tweak. a displacement map you can watch being generated. the kind of thing that makes the math tangible.

references