Skip to content

description: What LiquidGlass does not do yet, platform by platform: the honest list of gaps between it and iOS 26's Liquid Glass.

Limitations

Current development gaps

See the visual status guide for the exact tested checkpoints. Current open items include complete motion/contour/ink agreement across the supplied gestures, Android endpoint equivalence, ordinary input/display performance and final Atlas acceptance. Native.NET has whole-surface feedback and a standard WinForms control-capture path; it does not yet supply the navigation-selector controller or universal native-widget capture. Other native frameworks remain integration work. Historical measurements below must not be read as current device proof.

This page separates current integration limits from historical measurement gaps. Full iOS parity is not established. New renderers and capture paths may change these limits; support requires tests on the actual host, not only a successful shader compilation.

Android 13+ for the real material

RuntimeShader is API 33. Below that there is no optical path at all — the material degrades to a tinted surface with the same rim lighting. It does not provide shader refraction.

Supply a fallbackSurface whenever the glass carries labels, or they will be unreadable there. Check LiquidGlassSupport.hasShaders if you want to change the design rather than the colour.

There is no system backdrop — every element pays for its own

Apple's material is composited by the OS. Android has no equivalent, so LiquidGlassTheme records what the app draws below it into a layer and hands it to the shader. Consequences:

  • An extra full-screen layer for the recording, and a RenderEffect per glass element.
  • Glass can only sample what your app drew inside the theme. Never the wallpaper, never another app, never the system UI beneath. A "translucent" status bar is not available to you.
  • Glass does not see other glass. The recording holds no glass, so a glass element over another refracts the content beneath both. GlassTabBar composes its lens over its own bar internally; arbitrary stacked glass does not reproduce that appearance.

Glass draws above content that is not glass

LiquidGlassTheme draws every glass element, with its content, in an overlay above the recorded screen, so that no glass samples itself. The consequence is z-order: a glass element is drawn above every non-glass element inside the theme, including ones that come later in the tree or sit in a layer above it, such as a sheet or a menu drawn as a later sibling. Its layout, hit targets and semantics stay where they were, so a covering element still receives the touches.

Keep content that must cover glass out of the glass's area, or switch the glass off while it is covered (Glass.Identity). Skia's own backdrop would draw glass in place on desktop and web and remove this limit there, but it blanks glass under a translucent ancestor (next section), so it is not planned (spikes).

An ancestor's alpha does not fade glass

For the same reason, glass is not drawn by its parent, so an ancestor's alpha or graphicsLayer { alpha = … } fades the element's siblings but not the glass or its content. Fade glass by changing its Glass (to Glass.Identity, or through animateGlassStyle), or with GlassTabBar's materialize. Skia's own backdrop would not help here: inside a translucent layer its backdrop is empty, so the glass goes blank instead of ignoring the fade.

Glass inside a scroll

With the state-and-modifier API (removed on 18 October) a glass element inside a scrolling container whose backdrop source was outside it could not find its own offset, and the backdrop slid out from under it as the content scrolled.

LiquidGlassTheme records the whole screen and places each glass element by its position in the root, so glass inside a scroll is an ordinary arrangement now. A render test in the desktop suite (GlassEffectTest) places a lens inside a scrolled Column and checks that it magnifies the page behind it and not itself. It has not yet been checked on an Android device.

No vibrancy

Apple's biggest legibility mechanism for content on glass is automatic and lives inside their compositor. A fragment shader drawn behind content cannot recolour that content.

Backdrop-driven inversion was the workaround, and it is coarser: one decision per element per frame, not per glyph. It is internal since 18 October; choose the appearance with LiquidGlassTheme(dark = …). See Accessibility.

No Reduce Transparency setting

Android has none, at any API level. LiquidGlassTheme(reduceTransparency = …) must be driven by an in-app control. Reduce Motion has a real analogue; Increase Contrast has a partial one that is about text rather than materials.

The interior blur is grainy at large radii

Nineteen taps cannot blur legible text, so the tap disc is rotated per pixel and the ghosting becomes noise. Noise reads as blur — but at large radii it is visibly noise.

A separable Gaussian would be cleaner but has to run before the shader, which destroys the sharp rim. That is the backdropBlur knob, and it is exactly the trade GlassStyle.Chrome makes. Apple's composited pipeline has no such either/or.

Chrome loses the signature edge, deliberately

Over a plain dark ground with sparse high-contrast text there is almost nothing to refract, and the little there is arrives half-legible, smeared along the rim and sitting under the labels. GlassStyle.Chrome uses a real pre-shader blur and a shallow bend, trading Apple's most recognisable feature for legibility. This is a real cost, accepted knowingly, and a direct consequence of the point above.

Path shapes rebuild on resize

The distance field for an arbitrary path is measured, not computed — see Shapes. It is rebuilt whenever the shape or size changes, on the calling thread. Animating a path shape's size continuously will rebuild every frame and you will feel it.

Morph identity is partial

Apple's morphing is identity-driven (glassEffectID, glassEffectUnion). Modifier.glassEffectID covers the first: inside a GlassEffectContainer, a piece that appears where another with the same id was grows out of the old outline with a spring. It is not Apple's transition; its timing is authored, not measured. There is no glassEffectUnion. Forcing shapes into one silhouette at rest has to be emulated by giving the container an artificially large spacing, which also inflates the bevels — an approximation, not an equivalent.

Eight pieces per container

SkSL needs an unrollable loop, so the cap is compiled in. Every piece is evaluated at every pixel, which is precisely what lets the fields interact, and is also why the cap exists.

Historical refractive profiles contain a caustic fold

Where displacement is steep the sampling Jacobian passes through zero — that fold is the compressed inverted rim image that defines the material. AGSL exposes no per-pixel mip bias over a recorded layer, and an isotropic bias would blur the tangential direction and destroy the sharp rim anyway. The mitigations are bounded displacement (the exact-Snell form self-bounds) and a 1.5px rim guard, not filtering.

This describes the historical material/navigation profiles. The new GlassProfile.Lens uses a monotonic continuous map without that internal fold; it is intended for free magnifiers.

Desktop evidence covers Windows, not every desktop backend

Atlas has native Windows Direct3D screenshots plus desktop-renderer gesture and optical tests in the stabilization record. The record labels native captures separately from test-renderer images. Static redraw/submission timings do not establish presented-frame latency. macOS, Linux desktop GPUs and current Android physical-device behavior have not been verified for this revision.

Containers are chrome at rest

GlassEffectContainer runs the measured optics too, but a container has no drag to track, so its lens formation is fixed at 0: fused bodies get the two-kernel tone, the lift and the inward rest profile, never the fold. Its pieces also share one Glass, so a container cannot mix roles.

The measured tone is within a few levels, not exact

Against the phone, same test image, same tools: light in-app glass lands within 6 levels of the reference at every Tint Amount and the edge profile within 5; dark in-app glass over white reads 6-11 levels low above the default Tint Amount. The phone's tone responds more to a brightening neighbourhood than to a darkening one, which a symmetric blur in either sRGB or linear light does not reproduce; the residual is documented in the measured model's section 11 and in the calibration study.

The rest lens is measured at one size, and selected per style

The corner lens of chrome at rest has two source maps, selected by GlassStyle.restMap. GlassRestMap.Legacy, the constructor's default (and the tab bar's), is the map that shipped: the ring 0.55 W inside mapped onto the outer band. GlassRestMap.Measured is the resting-corner table fitted in the closeout on 190 display-to-source landmark pairs read off the Photos toolbar's 72 px ends (24 pt on the phone, the library's 24 dp design unit by convention) in both appearances over both calibration targets; against the same pairs the shipping map is 6-7 px off at every depth from 6 to 26 px and the table is within 1 px. It changes only the resting corner: the fully formed fold, the straight-run term, the held-lens family, coverage, padding, normals, dispersion and lighting are untouched, and formation blends it toward the same fold. GlassStyle.inApp and toolbar use it by default since 26 October: over the calibration chart it gives their caps the dark fold iOS shows (evidence).

What it does not establish: any other radius. The table is dimensionless in u = d / W, so at another radius the shader evaluates the same curve scaled by the band — an explicit scale extrapolation, verified for implementation consistency on the JVM and on a Pixel 7 at the Vitals nav bar's 31 dp corner, not measured against the phone there. The shallow region under the rim (d < 6 reference px) is a constant extension of the first knot, also unmeasured. The width of the blend between an arc and a straight run cannot be seen over vertical stripes; the library blends over 0.2 R along the edge. Larger arcs (the dock's 126 px ends) did not measure through the Home Screen wallpaper's warped reference. All three are on the capture list.

Two things the closeout's render comparison also shows and this map does not fix: the library's interior stripe rendering under the toolbar has less contrast than the phone's, so the landmark estimator cannot localise the render at depths past about 26 px (a photometry question, not a geometry one); and the content and container shaders difference the distance field for their normal where the panel shader uses the closed form, which puts the three within 1.5 px of each other at a corner rather than within a quarter pixel.

The toolbar role (GlassStyle.toolbar) matches the phone within 4 levels at every slider position, but over the stripes it was measured on; its opacity and fine share were solved with the kernels held at the pill's, and a different split (a 230 grey at 0.70 with more fine detail) fits the same stripes. Over a flat backdrop the two readings differ by up to 15 levels. A capture of a toolbar over a flat region settles it.

The deforming selector costs several milliseconds a frame under a continuous drag

The opt-in deforming selector (GlassTabBarStyle.selector) steps a small constrained body on a 1/240 s clock and composites its ink through one more offscreen layer than the capsule selector does. Measured on a Pixel 7 against the same scene and the same injected drag, alternated, 30 s runs: its median completion latency is 4 to 6 ms higher than the capsule path's, and its p95 frame cost is higher too. Neither selector meets a 90 Hz budget while a lens is being dragged, and that part is the baseline's own limitation.

Where the cost is has not been established, and an earlier version of this page said otherwise. The extra time shows up in the animation/frame-callback stage (about +1.2 ms mean), in draw recording (about +1.0 ms) and in GPU time (about +0.6 ms mean) — so it is not "entirely CPU-side", and no trace attributes the completion-latency delta to any one of them. The idea that a small per-frame cost is amplified by a pipeline already over budget is a plausible explanation, not a measurement.

If those milliseconds matter more than the deformation, leave selector null.

The straight-run fold's strength is authored

GlassStyle.edgeFold produces the right structure at a straight edge — one fold, page content appearing twice with opposite orientation, which is what makes text bend into the edge instead of showing through it — and its turning point and paired-image separation are verified against the polynomial that defines it. But the strength itself is a design constant. It was not fitted to a capture, and the reference stills it was compared against are over a different page than ours.

The tab-bar lens carries its content a frame late while it moves

The lens draws the selected copy of the row through its own optics, pinned to the bar so it lands on the resting copy; held still — on a tab or between two — the copies coincide. While the lens is moving the copy inside it lags a frame, so a glyph shows two colours for that frame. The reference does the same (its content sits 12-20 px toward the lens centre mid-drag in the iOS 27 recording), so it is left as it is.

The detail's sharp axis is assumed to be the element's long one

The material's fine term is a high-pass along a bar and nothing across it, which is what the phone does (measured model, section 2a). Both elements it was measured on are horizontal bars, so the captures cannot say whether the sharp axis is the screen's x or the element's own long axis. The shader takes the long axis, which is the same thing for every bar and toolbar and a guess for a tall element. A vertical glass element over a striped backdrop would settle it.

The pill's dark contour is not drawn

The phone's pill has a one-pixel dark contour at its outermost pixel. It is not drawn, because the shader's contour sits 1.5 px inside the edge where the measured bead peaks (measured model, FINDINGS 21).

Astra review limits

The shared anchored interaction is documented in generic interaction. The nav pose controller and generic material transform remain distinct. Parent-driven GlassPressSource.press(point, pullOffset) now exposes cumulative parent-driven pull; normal self-interacting controls do not need it. Rim chroma and exact original pointer timing remain unresolved. The historical/Android endpoint error is 1.4256 code values against a 1.0 limit; the internal 1.5 tolerance is not a strict pass. Separate-input Desktop Calm now measures 0.9569 on the same gate; see endpoint precision for its scope and backend costs. Optional glass cards stay off by default because their measured cost is high. See implementation status.

Continuous lens and calm response preview

GlassStyle.clearLens fixes the free magnifier's internal map discontinuity. It is an authored elliptical magnifier field, not the measured held navigation profile or a full physical lens simulation. Its optional foreground pass uses the same source mapping. The factory suppresses dispersion; setting dispersion on this profile does not enable the navigation model's spectral bands.

GlassInteraction.Calm and GlassTabBarStyle.Calm() use authored input/timing choices. Original reference videos do not expose finger coordinates; screen-edge travel is tested as an interaction bound, not claimed as a recovered Apple input gain. Existing expressive presets remain available.

The dependency-free web preview supports the continuous lens only. The separate CanvasKit kit exports all three production passes, with a high-level clear-lens painter and lower-level material bindings. Both accept host-supplied backdrops; neither captures arbitrary DOM, protected video or other apps. Flutter, React Native, Qt Quick and Unity have ports with their own limits (Unity's has not been run in Unity yet); SwiftUI is not a target. See porting.