Glassmorphism in Flutter: BackdropFilter, Blur, and ImageFiltered
Frosted glass, blurred dialogs, and soft scrims all run through BackdropFilter — one of the most expensive widgets in Flutter. How it works, why it needs a clip, and how to make multiple blurs cheap.
Published on • October 14, 2026
AI Assistant

The frosted-glass header, the blur behind a modal, the scrim that dims a photo under a bottom sheet: in Flutter all of them come from one widget, BackdropFilter. It is also one of the few widgets where the API documentation opens with “this effect is relatively expensive.”
Understanding exactly what it blurs, what bounds the blur to, and when you should not use it at all is the difference between a smooth glass panel and a janky screen.
What BackdropFilter actually does
BackdropFilter applies an ImageFilter to the content that has already been painted beneath it, then paints its own child on top:
ClipRect(
child: BackdropFilter(
filter: ui.ImageFilter.blur(sigmaX: 16, sigmaY: 16),
child: Container(
color: Colors.white.withValues(alpha: 0.35),
child: const Text('Frosted panel'),
),
),
)
Two things follow from that definition:
- The child is not blurred. The text stays crisp; the world behind it does not. If you want the child blurred too, that is
ImageFiltered— a different widget entirely. - The blur is bounded by the nearest ancestor clip, not by the child. Without a clip, the filter applies to the entire screen. The
ClipRectabove is not decoration; it is what limits the blur to the panel’s own rectangle.
That second point is the source of most “my blur covers everything” bugs. The canonical shape is clip first, filter second, child third.
Blur strength: sigma is not a pixel radius
sigmaX and sigmaY are Gaussian standard deviations. A sigma of 4–8 is a believable frosted panel; 16–40 is an abstract soft field. Values look different per platform because the underlying rasterizer approximates the kernel — the same sigma on Android (Impeller) and iOS can differ slightly, so tune against both.
The blendMode parameter controls how the filtered background blends back in. BlendMode.srcOver is the only value supported on every platform and is right for nearly every scene. The docs call out one exception: when a parent uses a temporary buffer — a saveLayer, which Opacity triggers — BlendMode.src can produce more pleasing results. If a blur inside an Opacity widget looks washed out or oddly doubled, that is the knob.
The performance reality
A blur reads the framebuffer behind the widget, convolves it, and writes it back. Non-local filters like blur touch many pixels, so cost scales with area, not complexity. A 60×60 chip is nearly free; a full-screen blur rebuilt every frame during a scroll is not.
Practical rules:
-
Prefer
ImageFilteredwhen you are blurring one widget. The docs are explicit: applying a blur to a single sibling throughBackdropFilteris “unnecessarily expensive,” whileImageFiltereddoes the same job with dramatically better performance. If there is no content behind the thing you are blurring, you do not need a backdrop filter at all.// Blurs only the image — cheap ImageFiltered( imageFilter: ui.ImageFilter.blur(sigmaX: 6, sigmaY: 6), child: Image.asset('hero.png'), ) -
Clip to exactly what you blur. A
ClipRectthat matches the child keeps the filtered region small. AClipRRectadditionally rounds the blur to your card shape. -
Do not rebuild the filter every frame. Hoist
ui.ImageFilter.blur(...)into aconstor a field. Creating a new filter object perbuildduring an animation forces the render object to update every frame. -
Animate opacity, not sigma. To fade a blur in, animate the child’s color alpha (or an
AnimatedOpacityoverlay) rather than interpolating sigma — a changing sigma re-runs the full convolution each frame. -
Be careful under
OpacityandRepaintBoundary.Opacity’s save-layer behavior interacts withblendMode, and an uncached backdrop region repaints whenever anything behind it moves.
One blur for many widgets: BackdropGroup
A list of frosted rows — a notification shade, a translucent settings list — normally means one expensive backdrop blur per row. Flutter added a way to batch them: when several BackdropFilters share a BackdropKey, the engine performs the filtering once and reuses the result.
BackdropGroup(
child: ListView.builder(
itemCount: 60,
itemBuilder: (context, index) {
return ClipRect(
child: BackdropFilter.grouped(
filter: ui.ImageFilter.blur(sigmaX: 40, sigmaY: 40),
child: Container(
color: Colors.black.withValues(alpha: 0.2),
height: 200,
child: const Text('Blur item'),
),
),
);
},
),
)
BackdropFilter.grouped looks up the nearest BackdropGroup and joins it; you can also pass a backdropGroupKey manually. The visual result is identical to N independent blurs while the engine runs one — for a scrolling list of translucent rows, this is the difference between 60fps and something you can feel.
The constraint: overlapping filters must not share a key. If two blurred regions overlap while sharing a BackdropKey, the overlap looks as though only one filter was applied. Group by non-overlapping sets.
Recipes
Frosted app bar
SliverAppBar(
pinned: true,
expandedHeight: 200,
flexibleSpace: ClipRect(
child: BackdropFilter(
filter: ui.ImageFilter.blur(sigmaX: 20, sigmaY: 20),
child: Container(
color: Theme.of(context).colorScheme.surface.withValues(alpha: 0.6),
alignment: Alignment.bottomLeft,
padding: const EdgeInsets.all(16),
child: const Text('Gallery'),
),
),
),
)
Blurred dialog scrim — put the Stack’s background in, then a Positioned.fill containing the clip + filter + a ColoredBox for the dim, then the dialog. The dialog itself is not blurred; the world behind it is.
Blur-on-hover card — blur the image inside the card with ImageFiltered when hovered, not the backdrop. Hover transitions then animate only that subtree.
Gotchas
- Blur over platform views (web views, maps, video) has restrictions. On iOS a
PlatformViewcannot always be blurred; Flutter documents the limitation and the supported workaround for the platform-view backdrop blur case. BackdropFilterinside an unclippedStackblurs the whole screen. Always clip.- Semantics and text stay sharp — the filter never touches the child, so accessibility and text selection are unaffected.
- Test on a low-end device. Blur cost is GPU-bound and the gap between a flagship and a budget phone is much larger here than for ordinary layout.
- Consider a gradient instead. Many “blurred” scrims are really just translucent gradients; a
ShaderMaskorDecoratedBoxis an order of magnitude cheaper and often indistinguishable.
Checklist
- Clip first:
ClipRect(orClipRRect) wraps theBackdropFilter. - Use
ImageFilteredwhen blurring a single widget rather than the backdrop. - Hoist the
ImageFilterout ofbuild; animate alpha, not sigma. - Batch repeated blurs with
BackdropGroup+BackdropFilter.grouped, but never share a key between overlapping regions. - Keep
blendMode: BlendMode.srcOverunless you have measured a reason not to.
Sources: BackdropFilter API, ImageFiltered API, Flutter documentation.