Writing · 2026
At WWDC25 Apple replaced the flat, blurred materials it had used since iOS 7 with Liquid Glass: a translucent material that “reflects and refracts its surroundings, while dynamically transforming to help bring greater focus to content.” (Apple Newsroom) The last part matters more than the optics. Glass is a tool for deciding where the eye goes.
The idea is a strict split into two layers. Content (photos, documents, messages) sits at the bottom and fills the screen. Controls and navigation form “a distinct functional layer” that floats above it. (HIG) Glass belongs only to the upper layer. Because it lets the content show through, the content stays the subject even under the toolbar; because it bends light at its rim, the control is still unmistakably a separate object you can touch.
That is the attention trade the whole system is built on. An opaque bar is always legible but claims a strip of the screen and a hard edge. A plain blur keeps the content but makes the control feel like a smudge. Glass is the middle: the control is present, readable, and visually quieter than the content, until you touch it.
The glass
One slab, with real thickness. It bends what is behind it and stays quieter than what it covers — which is the whole reason to use it.
The content
The page keeps its alignment and its contrast underneath. Nothing is dimmed, blurred away or rearranged by the glass resting on it.
The bend
The lines under the slab are drawn larger, as a lens shows them: most at the middle, and exactly unchanged where they meet the rim.
Apple's “Meet Liquid Glass” session breaks the material into layers. Lensing comes first: “where previous materials scattered light, this new set of materials dynamically bends, shapes, and concentrates light in real time.” Then highlights from light sources in the environment that respond to the geometry and, in places, to device motion. Then a shadow that is itself adaptive, darker over text and lighter over a flat, light background. Finally adaptivity: tint and dynamic range shift continuously so that what sits on the glass stays legible. (WWDC25 219)
The playground below implements each of these as a parameter. Drag the glass over the clock and across the waves of the macOS Tahoe wallpaper: the rim magnifies and compresses what is under it, the centre stays nearly undistorted, and a thin highlight runs around the edge facing the light. Switch the view to displacement field to see where the refraction actually happens, only in the bezel.
The flat
The middle rays cross the glass and come out on exactly the line they went in on: at a flat wall there is nothing to turn, and the turn really is zero. The middle of a control shows what is behind it undistorted.
The shoulders
Over a shoulder the two walls have turned, so the same ray is pulled off its line — once at each wall, both bends agreeing. Every ray higher up the shoulder is turned further, so the rays converge.
A few things are worth trying. Set thickness to 0 and the shape collapses into an ordinary frosted rectangle; the rim is what makes it read as an object. Push ior above 2 and the edge starts to look like a cheap fisheye. Switch the profile to convex circle and the edge gets a hard crease where the dome meets the flat top; the squircle is smoother when the shape is stretched into a capsule, which is why most web implementations default to it.
| parameter | what it changes | Apple’s equivalent |
|---|---|---|
| thickness · ior | How far light bends at the rim | Lensing; larger glass simulates thicker material |
| bezel · profile | Where the bending happens and how sharp the edge looks | The curved edge of each control |
| frost blur | How much detail survives under the glass | Regular blurs more, clear barely blurs |
| frost tint · adapt | How much the glass leans light or dark to keep glyphs legible | Regular adapts and flips; clear does not |
| tint | A colour mapped onto the content brightness | .tint(_:), only for the primary action |
| dispersion | Colour fringing where the bend is strongest | Not named by Apple; a web-side touch |
| specular · light angle | The thin bright rim and its direction | Highlights that respond to geometry and motion |
| shadow | Separation from what is below | Adaptive shadow, stronger over text |
| merge distance | How close two shapes get before they melt together | GlassEffectContainer(spacing:) |
The beam
Three parallel rays, collimated, running into the left face. The cut they cross is the near face of the wedge, so the path through the glass is drawn with them rather than left to the imagination.
The turn
Every ray is bent twice, once at each face, and both bends turn it the same way, down toward the base. The dashed line beside it is where the ray would have gone unbent; the angle between the two is the deviation, and all three carry it.
The spread
The glass does not bend every colour alike, so the three no longer leave together: they open into a fan, and its opening is the slider. At nothing they leave parallel and alike — a prism does not focus, it deviates.
One pin, three times
The same pin stands on each pane. What you see is three of them, a step apart, each step the one sheet of glass bending the view of the one behind it.
The pile
Each pane above is set further back and a little smaller, so the front edge of every pane stays clear and the pile reads as a stepped thing, not a flat repeat.
The far pane
The pin standing on the far pane is seen directly. Every pin nearer the viewer is that same pin looked at through one more sheet of glass, so it has walked one more step and is drawn in the dimmer stroke.
There are two variants and they should never be mixed. Regular “blurs and adjusts the luminosity of background content to maintain legibility”; most system components use it, and it is the right choice whenever the glass carries text. (HIG) Small regular elements such as tab bars even flip between a light and a dark appearance depending on what is behind them. Large ones (menus, sidebars) adapt but do not flip, because “their surface area is too big.” (WWDC25 219)
Clear is “permanently more transparent” and has no adaptive behaviour. Apple allows it only when three things are all true: the element sits on media-rich content, a dimming layer will not hurt that content, and what sits on the glass is bold and bright. (WWDC25 219) Over bright content the guidance is a dark dimming layer of 35% opacity. (HIG) In SwiftUI that is literally .glassEffect(.clear).background(.black.opacity(0.3)). (SwiftUI)
Drag the control across the seam below. The two halves are the same Tahoe wallpaper in its light and dark appearance. With regular, the glyph turns dark over the light one and light over the dark one, and the shadow strengthens where the waves are busiest. With clear, nothing adapts: the white star disappears against the bright half until you add the dimming layer.
Tint is not a fill. Selecting a colour “generates a range of tones that are mapped to content brightness underneath,” so the glass still changes with what is behind it; a solid fill “breaks the visual character of Liquid Glass.” (WWDC25 219) Tint is an attention device and is meant to be scarce: emphasise primary actions by colouring the background, not the symbol, and “refrain from adding color to the background of multiple controls.” (HIG)
The “liquid” half of the name is about continuity. “As you go between states in an app, Liquid Glass dynamically morphs between the controls in each context. This maintains the concept of having a singular floating plane.” (WWDC25 219) In code this is a GlassEffectContainer: glass shapes inside one container are rendered together, and “as shapes near one another, their paths start to blend into one another.” The container's spacing sets how near is near: “the higher the spacing, the sooner blending begins.” (SwiftUI)
On the web the same behaviour falls out of signed distance fields. Each shape is a function that returns the distance to its edge; a polynomial smooth minimum of two such functions grows a neck between them once they are closer than k. (Inigo Quilez) Because the refraction normal comes from the gradient of the merged field, the bend flows across the neck as if it were one piece of glass. Tap + to grow two tools out of the third, drag the drop into the toolbar, and turn on the distance field to see the merged shape.
One material
A button, a capsule and a card, cut from the same glass. The distance between two outlines is a number, and this is the number the slider changes.
Where they meet
Bring two outlines within the blend distance and they do not stop and touch — they curve in, across and back out. That neck is not a joint drawn between two objects; it is the edge of one merged surface.
Still apart
The card is out of reach, so it keeps its own outline and the background runs round it. Turn the slider up and even this gap closes.
At the small end
A button and a switch: the sizes a finger actually meets. The material is the same as the slab — only the area it has to work with is smaller.
Through the middle
Each control keeps the features that make it that control: the switch has its knob at one end of its track, the slider has a groove to ride in.
At the large end
A big knob, where there is room for the lensing to read. The bar is graded so the swell of the material is the thing you notice, not the shapes.
// Signed distance to a rounded rectangle: < 0 inside, 0 on the edge.float sdRoundRect(vec2 p, vec2 halfSize, float r) { vec2 q = abs(p) - halfSize + r; return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - r;}// Polynomial smooth-min (Inigo Quilez). k is the merge distance in px:// two shapes farther apart than k stay separate; closer, they grow a neck.float smin(float a, float b, float k) { float h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0); return mix(b, a, h) - k * h * (1.0 - h);}float scene(vec2 p) { float d = sdRoundRect(p - uCenter[0], uHalf[0], uRadius[0]); for (int i = 1; i < N; i++) d = smin(d, sdRoundRect(p - uCenter[i], uHalf[i], uRadius[i]), uK); return d;}Menus use the same idea at a larger scale. Buttons “fluidly morph into menus and popovers,” so the menu visibly comes from the control that summoned it and returns there when dismissed. (Apple) As glass grows it also changes character: it simulates “a thicker, more substantial material,” with deeper shadows, stronger lensing and softer scattering. (WWDC25 219) When there is no source to morph from, glass materializes: “instead of fading, Liquid Glass objects materialize in and out by gradually modulating the light bending and lensing.” (WWDC25 219) Compare the three below; the plain fade is the one that loses track of where the menu came from.
At rest, glass is quiet. Under a finger it “responds to interaction by instantly flexing and energizing with light,” with “an inherent gel-like flexibility,” and “the material illuminates from within … starting right under your fingertips, the glow spreads throughout the element and onto any Liquid Glass elements nearby.” (WWDC25 219) In SwiftUI this is .interactive(), which makes glass react “by scaling, bouncing, and shimmering.” (WWDC25 323) Some controls only become glass while you use them: for sliders and toggles, “the knob transforms into Liquid Glass during interaction.” (Apple)
This is the second half of the attention design. Making the resting state quiet only works if the active state is unmistakable. Press the capsule below and drag: it swells, leans toward your finger with diminishing returns, glows from the touch point, and the glow reaches the small circle beside it. Drag the slider and its plain knob lifts into a glass lens over the track.
// Every motion in the demos is a spring, not a tween: it can be retargeted// mid-flight (tap + twice quickly) and it overshoots a little, like a gel.function step(s, dt) { const a = -s.stiffness * (s.value - s.target) - s.damping * s.velocity; s.velocity += a * dt; s.value += s.velocity * dt;}// Press: scale up, glow from the touch point, lean toward the finger.onPress(e) { scale.target = 1.12; glow.target = 1; glowAt = point(e); }onMove(dx) { lean.target = 60 * Math.tanh(dx / 120); } // diminishing returnsonRelease() { scale.target = 1; glow.target = 0; lean.target = 0; }Two behaviours exist purely to hand attention back to the content. When you scroll down, “tab bars shrink to bring focus to the content,” and “the moment users scroll back up, tab bars fluidly expand.” (Apple Newsroom) At the screen edges, a scroll edge effect replaces the old opaque bar background: it “gently dissolves the content into the background” and, over dark content, “intelligently switches to apply a subtle dimming instead.” (WWDC25 219) The HIG is explicit that these effects “aren't decorative. They don't block or darken like overlays,” and asks for one per view. (HIG)
Scroll inside the phone. The tab bar collapses to its active tab and expands again when you scroll up or tap it; the search button stays put. The top edge dissolves light cards into the paper and dims dark ones, and the account button and status bar flip their glyphs to match what is behind them. The hard style is the uniform band Apple uses with pinned headers.

App icons are a place where the same material reads as something else: a menu that grows out of the icon you are holding. Press and hold the Settings icon below. The icon lifts and swells, and the menu materializes from it rather than appearing beside it, which is what tells you which of the sixteen icons the menu belongs to. Let go or press Escape and it goes back.
Every rule Apple gives about where to use the material is an attention rule. “Don't use Liquid Glass in the content layer”: a glass table view “would make it compete with other elements and muddy the hierarchy.” (HIG) (WWDC25 219) “Always avoid glass on glass”: when something sits on glass, give it fills and vibrancy instead. (WWDC25 219) “Use Liquid Glass effects sparingly … limit these effects to the most important functional elements in your app.” (HIG) And in steady states, such as launch, “avoid intersections between content and Liquid Glass.” (WWDC25 219)
The figure below puts the same wallpaper gallery under three treatments. The opaque bar is legible but hides a row and adds a hard edge. Glass on navigation only keeps the wallpapers as the subject, keeps the toolbar present but quiet, and lets a single tinted button announce the next step. Glass everywhere turns six captions into six competing lenses, and the toolbar no longer stands out because nothing does.
Put together, the model is simple. Attention has three levels. Content is the default and gets no glass. Navigation is present, readable and quiet: regular glass, monochrome glyphs, adaptive shadow, out of the way when you scroll. The next step is loud only where it needs to be: one tint, one interactive response, one morph from the place you touched. Glass is useful exactly to the extent that it keeps those levels apart.
People have rebuilt the effect in three ways. The SVG route computes a displacement map and applies it with feDisplacementMap. Chris Feijoo's write-up derives the map from a surface height function and Snell's law, sampling 127 points along one radius and reusing them around the shape. (kube.io) Shu Ding's snippet generates the map with a JavaScript “fragment shader” over a rounded-rectangle SDF (GitHub), which liquid-glass-react builds on, adding chromatic aberration by displacing each channel at a slightly different scale. (GitHub) Pup Atlas shows a lighter variant made of gradients, turbulence and inset shadows. (Atlas Pup Labs)
// kube.io's approach for SVG: precompute the bend once, along one radius.const n1 = 1.0, n2 = 1.5;const squircle = x => Math.pow(1 - Math.pow(1 - x, 4), 0.25);function profile(f, bezel, thickness, samples = 127) { const out = []; for (let i = 0; i < samples; i++) { const x = i / (samples - 1), e = 0.001; const slope = (f(Math.min(1, x + e)) - f(Math.max(0, x - e))) / (2 * e); const theta1 = Math.atan(slope * thickness / bezel); // angle of incidence const theta2 = Math.asin((n1 / n2) * Math.sin(theta1)); // Snell out.push(f(x) * thickness * Math.tan(theta1 - theta2)); // inward shift, px } return out;}// Then for every pixel: distance to the border -> magnitude from profile(),// border normal -> direction; write 128 + 127 * (dx, dy) / max into R and G.The catch is support. backdrop-filter: url(#filter), which is what makes the SVG route refract the live page, works only in Chromium; Safari and Firefox limit backdrop-filter to the built-in functions. (LogRocket) The usual pattern is a blur-and-shadow base for everyone, with the refraction layered on after feature detection.
<svg width="0" height="0" aria-hidden="true"> <filter id="lg" color-interpolation-filters="sRGB"> <feImage href="data:image/png;base64,..." result="map"/> <!-- the R/G map --> <feDisplacementMap in="SourceGraphic" in2="map" scale="48" xChannelSelector="R" yChannelSelector="G"/> </filter></svg><style>.glass { /* every browser */ backdrop-filter: blur(4px) saturate(150%); -webkit-backdrop-filter: blur(4px) saturate(150%); box-shadow: inset 0 1px 0 rgb(255 255 255 / .55), 0 8px 24px rgb(0 0 0 / .14);}.chromium .glass { /* set from JS: only Chromium refracts */ backdrop-filter: url(#lg) blur(1px) saturate(150%);}</style>The WebGL route trades live DOM for full control. You render the content into a texture and run a fragment shader that does everything per pixel. liquid-glass-js captures the page with html2canvas (GitHub); liquidGL ships its own rasteriser with a WebGPU-to-CSS fallback chain (GitHub); a Zenn tutorial walks through SDF, gradient normal, bezel curve, aberration and specular in about a hundred lines. (Zenn) The demos on this page take this route, and the shader is small enough to read in full. It works in every browser with WebGL; the cost is that the glass refracts a canvas, not arbitrary HTML.
// Height of the glass across the bezel: t = 0 at the edge, 1 where it turns flat.float height(float t) { // convex squircle t = clamp(t, 0.0, 1.0); return pow(1.0 - pow(1.0 - t, 4.0), 0.25);}void main() { vec2 p = pixel(); // css px, y down float d = scene(p); if (d > 0.0) { gl_FragColor = vec4(content(p), 1.0); return; } // 1. Normal. The SDF gradient points outward; the profile slope says how steep. vec2 g = normalize(vec2(scene(p + vec2(1, 0)) - scene(p - vec2(1, 0)), scene(p + vec2(0, 1)) - scene(p - vec2(0, 1)))); float t = -d / uBezel; float slope = (height(t + 0.01) - height(t - 0.01)) / 0.02; vec3 N = normalize(vec3(g * uThickness * slope / uBezel, 1.0)); // 2. Snell's law. A ray looking straight down enters glass with n = 1.5, // bends, and travels the local glass height before it hits the content. vec3 R = refract(vec3(0, 0, -1), N, 1.0 / uIor); vec2 offset = R.xy / -R.z * uThickness * height(t); // 3. Dispersion: red and blue bend by slightly different amounts. vec3 c = vec3(frosted(p + offset * (1.0 + uCA)).r, frosted(p + offset).g, frosted(p + offset * (1.0 - uCA)).b); // 4. Light: a thin rim facing the light, a weaker one opposite, Schlick fresnel. vec2 L = vec2(cos(uLight), sin(uLight)); float rim = exp(d / 1.4); float spec = rim * (0.85 * max(dot(g, L), 0.0) + 0.38 * max(-dot(g, L), 0.0)); float fresnel = 0.04 + 0.96 * pow(1.0 - N.z, 5.0); c += uSpecular * (spec + 0.18 * fresnel); gl_FragColor = vec4(c, 1.0);}Three details carry most of the realism. First, refraction lives in the bezel only: the height profile is flat in the middle, so its slope, and therefore the bend, is zero there and steep near the edge. Second, the offset is not a fixed lens distortion; GLSL's refract applies Snell's law, so thickness and ior behave like their physical counterparts. Third, the rim highlight is thin (an exponential of the distance, about a pixel and a half wide) and lit from one direction. A broad white gradient is what makes most imitations look like plastic.
// Regular glass leans light over light content and dark over dark content,// so whatever sits on it stays legible. Clear skips this step entirely.float L = luminance(frosted(p + offset));for (int i = 0; i < 4; i++) { // look a little around, not just under float a = float(i) * 1.5708 + 0.6; L += luminance(content(p + 44.0 * vec2(cos(a), sin(a)))) * 0.25;}vec3 frostColor = mix(vec3(0.06), vec3(1.0), smoothstep(0.22, 0.5, L * 0.5));c = mix(c, frostColor, uFrost);// Tint maps a hue onto the content brightness instead of painting a flat fill.c = mix(c, uTint * (0.55 + 0.6 * luminance(c)), uTintAmount);Adaptivity is a luminance sample. Regular glass looks a little around the pixel, not just under it, and leans its frost toward white or near-black; the demo also samples the content under each control on the CPU to choose the glyph colour and shadow strength, the same decisions Apple describes. Morphing is the smooth-min from earlier plus springs: every transition retargets a spring instead of running a fixed-length tween, so interrupting it never jumps.
On Apple platforms none of this is your job. The system components adopt the material automatically, and custom views get it from a handful of modifiers. (SwiftUI) (WWDC25 323) The design work is choosing where to put them.
// SwiftUI (iOS 26+)Text("Hello").padding() .glassEffect() // .regular, capsule .glassEffect(.regular.tint(.orange).interactive(), in: .rect(cornerRadius: 16))Label("Flag", systemImage: "flag.fill").padding() .glassEffect(.clear) .background(.black.opacity(0.3)) // clear needs a dimming layerGlassEffectContainer(spacing: 40) { // shapes within 40pt melt together HStack(spacing: 40) { Image(systemName: "pencil").glassEffect().glassEffectID("pencil", in: ns) if isExpanded { Image(systemName: "eraser").glassEffect().glassEffectID("eraser", in: ns) } }}Button("Done") {}.buttonStyle(.glassProminent)TabView { ... }.tabBarMinimizeBehavior(.onScrollDown)ScrollView { ... }.scrollEdgeEffectStyle(.hard, for: .top)Glass is only acceptable because it can be turned down. Reduce Transparency “makes Liquid Glass frostier and obscures more of the content behind it,” Increase Contrast makes elements “predominantly black or white” with a contrasting border, and Reduce Motion “disables any elastic properties.” (WWDC25 219) Apple has kept adding control since launch: iOS 26.1 added a tinted option (Apple Support), and iOS 27 adds a slider “from ultraclear to fully tinted,” alongside “more uniform refraction and improved contrast.” (Apple) The demos here follow prefers-reduced-motion (springs snap to their targets) and prefers-reduced-transparency (more blur, more frost), and every control is a real button, tab or range input with a label.
The demos are approximations. They refract a canvas (Apple's macOS Tahoe 26 and iOS 26 wallpapers inside Apple's iPhone 17 Pro bezel), not the page itself; the light is fixed rather than tied to device motion; the luminance sampling is a few taps, not Apple's adaptive tone mapping. Treat the code as a way to understand the material, and the guidelines as the part that actually transfers.