Using it efficiently
Pick the right piece, and the cost mostly takes care of itself.
Most glass performance problems are not tuning problems. They are a widget doing a job a cheaper widget was built for. This page is that decision.
Start from what you are building#
| What you want | Reach for | Why |
|---|---|---|
| Glass that has to be cheap, and ready to ship | Lite glass | The most efficient piece here: no shader, no capture, no lens budget. Frost, tint and a lit rim, drawn from plain canvas geometry. On Skia and the web, put the whole app on it (LiquidGlassEngine.liteGlassOnSkia = true) and ship — there is no background to capture and no performance to worry about. On either engine, a tinted button or a card with a rim that does not need to bend anything names a LiquidGlassLitePickup in the liteGlass of its style, instead of a backdrop shader on Impeller or a captured background on Skia. |
| One panel floating over a page | LiquidGlassLens | One read, one shader — two passes on Impeller once it blurs. Nothing beats it for refraction. |
| A bar, a page, and tabs | LiquidGlassScaffold | On Skia it captures the body once for all the lenses under it. On Impeller there is no capture at all, and it batches its chrome into one read. Adaptivity is wired in on both. |
| Many glasses | Batch | Any number of lenses at the cost of one read — on Impeller. On Skia the view already captures once for all of them, so a batch is inert there, not wrong. |
| A cluster that should read as one panel | Blend with smoothness: 0 | One surface, one material, one read — up to eight members. LiquidGlassGroup was this, and is deprecated. |
| Two shapes that should melt together | Blend | The metaball bridge is the whole point of it. |
| A control that resizes as its content changes | Morph | It measures the child. The two blobs are one blender pass, but a heavier shader than a lens's — LiquidGlassMorphMotion.plain is one lens on one spring, when you do not need the waist. |
| A slider or a switch | LiquidGlassSlider / LiquidGlassSwitch | Self-contained. On Impeller the thumb reads the live backdrop and nothing is captured; on Skia their own small view takes one snapshot of the track and runs live only while the thumb is held. Your page is never captured. |
The one question that matters on Skia#
Does the background actually move? On Impeller a lens reads the live backdrop and there is no capture to think about. On Skia and the web the expensive thing is rasterizing the background so the shader has something to read — and doing that sixty times a second for a gradient that never changes is the single most common waste in a glass UI.
Dart
// A background that never changes: one snapshot, refracted forever.
final controller = LiquidGlassViewController();
LiquidGlassView(
controller: controller,
backgroundWidget: const MyGradient(),
realTimeCapture: false,
child: const MyGlassUI(),
);
// …and again only when the background genuinely changed:
await controller.captureOnce();- A painted gradient, an image, a static illustration → capture once.
- A scrolling list, a video, an animating background → keep it live.
- A background that changes rarely → capture once, then
captureOnce()again when it actually changes.
A checklist before you ship#
- Is anything capturing that does not need to? One
LiquidGlassViewper screen, not one per widget. The slider and the switch bring their own small view: a view around the page is fine, one around each control is not needed. And if the page can live without refraction, lite glass captures nothing at all. - Is `pixelRatio` still at `1.0`? The glass bends and blurs what it reads, so it hides a soft capture well.
0.7is usually invisible and meaningfully cheaper. - Is the blur doing work you cannot see? On Skia, blur is the most expensive appearance knob on the capture path, and above roughly
7it stops looking like a real backdrop blur anyway. - Is the morph pill earning its second pipeline?
LiquidGlassPillMode.impellerOnlygives you the full pill where it is free and the light highlight everywhere else — unless the app is on lite glass, wherebothcosts nothing on Skia either. - Are lenses overlapping inside a batch? They must not. Pull the overlapping one out with
LiquidGlassBatch.exclude.
Mistakes that look like bugs#
| Symptom | Cause |
|---|---|
| The glass is a flat frosted blur with no bend. | On Skia or the web with no ancestor LiquidGlassView. There is nothing to refract, so it degrades — and says so once in the debug console. The same look with no notice is lite glass by choice, or the first lens of the app while its shader compiles. |
| The glass refracts an old version of the page. | realTimeCapture: false with no captureOnce() after the background changed. |
| Two lenses in a batch look like one. | They overlap. Every member samples the same copy, taken before any of them painted, so the upper one cannot see the lower one’s glass. |
| A lens inside a list goes black while the list is pulled past its end (Impeller). | Android’s stretch overscroll renders the scrollable into its own texture, which holds what the list painted and not the page under it. The lens keeps its place and refracts that — black wherever the list is empty behind it, for as long as the pull lasts. If it shows, drop the overscroll indicator on scrollables that hold lenses. |
| The morph does not animate when the content changes. | The children have no keys, so a child that kept its type was not seen as new. |
Free by construction#
touch: null— the default — adds no listener and no ticker.visibility: falseremoves the backdrop cost instantly, and the child with it.- Adaptivity samples only while something adaptive is on screen, at a few kilobytes and at most eight times a second — and not at all while no frames are being drawn.
LiquidGlassSliderandLiquidGlassSwitchnever capture your page: nothing at all on Impeller, one snapshot of their own track on Skia.- Lite glass — one lens, or the whole app per engine — compiles no shader, takes no capture and holds no lens slot.
Comments
Comments are GitHub Discussions — reply from either place.