liquid_glass_widgets 0.10.7
liquid_glass_widgets: ^0.10.7 copied to clipboard
A Flutter UI kit implementing the iOS 26 Liquid Glass design language. Features shader-based glassmorphism, physics-driven jelly animations, and dynamic lighting.
Liquid Glass Widgets #
Bring Apple's iOS 26 Liquid Glass to your Flutter app — 37 glass widgets with real shader-based blur, physics-driven jelly animations, and dynamic lighting. Works on every platform out of the box.
https://github.com/user-attachments/assets/2fe28f46-96ad-459d-b816-e6d6001d90de
Wanderlust — a luxury travel showcase built entirely with liquid_glass_widgets
Features #
- 36 glass widgets — containers, interactive controls, inputs, feedback, overlays, and navigation surfaces
- Real frosted glass — native two-pass Gaussian blur + shader refraction on Impeller; lightweight shader on Skia/Web
- Just works everywhere — iOS, Android, macOS, Web, Windows, Linux; rendering path chosen automatically
- Adaptive quality (experimental) —
GlassAdaptiveScopebenchmarks the device at startup and adjusts quality in real time:minimalon slow hardware,standardon mid-range,premiumon fast devices. Degrades on thermal throttle, recovers when cool - Zero dependencies — no third-party runtime libraries, just the Flutter SDK
- One-line setup —
LiquidGlassWidgets.wrap(child: myApp)handles shader prewarming, accessibility bridging, and root backdrop sharing; addGlassBackdropScopeper screen to prevent ghost artifacts on navigation (see Backdrop Isolation) - Gyroscope lighting —
GlassMotionScopedrives specular highlights from anyStream<double> - WCAG-compliant by default — Reduce Motion and Reduce Transparency are respected automatically; no setup required
Examples #
Wanderlust — Luxury Travel Showcase #
A premium app demonstrating liquid_glass_widgets in a real-world production context — full-bleed imagery, parallax scroll, hero transitions, and a concierge chat interface. This is the app shown in the video above.
cd example/showcase && flutter pub get && flutter run
Apple Music Demo — iOS 26 Replica #
A recreation of the Apple Music app demonstrating GlassSearchableBottomBar, a floating playback pill, and the full iOS 26 navigation model with smooth morphing transitions.
cd example && flutter pub get && flutter run -t lib/apple_music/apple_music_demo.dart
Apple News Demo — iOS 26 Replica #
A recreation of the Apple News app demonstrating GlassSearchableBottomBar with its morphing search pill, category chips, hero cards, and rounded article tiles.
cd example && flutter pub get && flutter run -t lib/apple_news/apple_news_demo.dart
Widget Showcase — Full Component Library #
A complete catalogue of all 36 widgets organized by category. Use it to explore every component, try live settings, and copy patterns directly into your app.
cd example && flutter pub get && flutter run
Widget Categories #
Containers #
GlassCard · GlassPanel · GlassContainer · GlassDivider · GlassListTile · GlassStepper · GlassWizard
Interactive #
GlassButton · GlassIconButton · GlassChip · GlassSwitch · GlassSlider · GlassSegmentedControl · GlassPullDownButton · GlassButtonGroup · GlassBadge
Input #
GlassTextField · GlassTextArea · GlassPasswordField · GlassSearchBar · GlassPicker · GlassFormField
Feedback #
GlassProgressIndicator · GlassToast · GlassSnackBar
Overlays #
GlassDialog · GlassSheet · GlassModalSheet · showGlassActionSheet · GlassMenu · GlassMenuItem
Surfaces #
GlassAppBar · GlassBottomBar · GlassSearchableBottomBar · GlassTabBar · GlassSideBar · GlassToolbar
Installation #
dependencies:
liquid_glass_widgets: ^0.10.7
flutter pub get
Quick Start #
Set up the library once in main.dart. initialize() pre-caches shaders and
registers the debug performance monitor. wrap() installs the root backdrop
scope and accessibility bridge:
import 'package:flutter/material.dart';
import 'package:liquid_glass_widgets/liquid_glass_widgets.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Async platform setup: shader prewarming + Impeller pipeline.
await LiquidGlassWidgets.initialize();
// Widget-tree composition: installs GlassBackdropScope (required).
// Enable adaptiveQuality to automatically tune glass quality per device.
runApp(LiquidGlassWidgets.wrap(
child: const MyApp(),
adaptiveQuality: true,
));
}
Accessibility is on by default. The library automatically reads the device's Reduce Motion and Reduce Transparency settings — no extra setup required. See Accessibility for details.
Then add any glass widget to your tree:
Scaffold(
appBar: GlassAppBar(title: const Text('My App')),
bottomNavigationBar: GlassBottomBar(
tabs: [
GlassBottomBarTab(label: 'Home', icon: const Icon(Icons.home)),
GlassBottomBarTab(label: 'Profile', icon: const Icon(Icons.person)),
],
selectedIndex: 0,
onTabSelected: (i) {},
),
body: const Center(child: GlassCard(child: Text('Hello, Glass!'))),
)
Platform Support #
| Platform | Renderer | Notes |
|---|---|---|
| iOS | Impeller (Metal) | Full shader pipeline, chromatic aberration |
| Android | Impeller (Vulkan) | Full shader pipeline, chromatic aberration |
| macOS | Impeller (Metal) | Full shader pipeline, chromatic aberration |
| Web | CanvasKit | Lightweight fragment shader |
| Windows | Skia | Lightweight fragment shader |
| Linux | Skia | Lightweight fragment shader |
Platform detection is automatic — no configuration required.
Glass Quality Modes #
Standard — Default, Recommended #
The right choice for 95% of use cases. Works on every platform with iOS 26-accurate glass effects.
GlassContainer(
quality: GlassQuality.standard, // this is the default
child: const Text('Great for scrollable content'),
)
Premium — Impeller Only #
Enables the full Impeller shader pipeline with texture capture and chromatic aberration. On Skia/Web, automatically falls back to Standard.
GlassAppBar(
quality: GlassQuality.premium,
title: const Text('Static header'),
)
Use Premium only for static, non-scrolling surfaces (app bars, bottom bars, hero sections). It may not render correctly inside
ListVieworCustomScrollViewon Impeller.
Minimal — Shader-Free #
Zero custom fragment shader cost on any device. Uses BackdropFilter blur + a Rec. 709 saturation matrix + a specular rim stroke. Visually equivalent to a high-quality frosted panel.
GlassCard(
quality: GlassQuality.minimal,
child: const Text('No shader overhead'),
)
Two ideal use cases:
- Device fallback — very old Android devices or any device where
ImageFilter.isShaderFilterSupportedisfalse - GPU budget management — use
minimalfor background panels and list cards while keepingstandardorpremiumon the focal element. A screen with 15 glass list cards runningminimalfires zero shader invocations during scroll
Theme shorthand:
GlassThemeVariant.minimalappliesminimalquality globally viaGlassThemeData.
Theming #
All widgets automatically inherit from GlassTheme and adapt to light/dark mode:
GlassTheme(
data: GlassThemeData(
light: GlassThemeVariant(
settings: GlassThemeSettings(thickness: 30, blur: 12),
quality: GlassQuality.standard,
),
dark: GlassThemeVariant(
settings: GlassThemeSettings(thickness: 50, blur: 18),
quality: GlassQuality.premium,
),
),
child: MaterialApp(home: MyHomePage()),
)
GlassThemeSettingsvsLiquidGlassSettings: UseGlassThemeSettingsinsideGlassThemeVariant. It accepts the same parameters but all are nullable — only the fields you explicitly set are applied; everything else inherits from each widget's own defaults.LiquidGlassSettingsis the full settings type used on individual widgets.
Access the current theme variant programmatically:
final variant = GlassThemeData.of(context).variantFor(context);
Glow Colors #
GlassGlowColors controls the interaction glow emitted by surfaces like GlassBottomBar and GlassSearchableBottomBar. Set it once in your theme and every navigation surface picks it up automatically:
GlassThemeVariant(
glowColors: GlassGlowColors(
primary: Colors.blue, // tab indicator & search pill glow
blurRadius: 12, // glow softness (default: 0 = crisp edge)
spreadRadius: 0.2, // glow spread beyond the widget edge (default: 0)
opacity: 0.8, // overall glow intensity (default: 1.0)
),
)
Individual widgets can still override via their interactionGlowColor parameter — the explicit param always wins over the theme.
Specular Sharpness #
Control the tightness of the specular highlight on any glass surface via LiquidGlassSettings.specularSharpness:
GlassCard(
settings: LiquidGlassSettings(
specularSharpness: GlassSpecularSharpness.sharp, // tight, mirror-like
),
child: ...,
)
| Value | Look |
|---|---|
GlassSpecularSharpness.soft |
Wide, diffuse — frosted / matte glass |
GlassSpecularSharpness.medium |
Default — matches iOS 26 |
GlassSpecularSharpness.sharp |
Tight, polished — mirror-like surface |
Each value maps to a fixed power-of-2 exponent. The GPU uses a zero-transcendental multiply chain for each — no pow() overhead.
Performance Tips #
LiquidGlassWidgets.initialize()at startup — pre-caches shaders, eliminates the white flash on first renderLiquidGlassWidgets.wrap()inmain.dart— installs root backdrop sharing and accessibility; passadaptiveQuality: truefor automatic per-device quality tuning. For multi-screen apps, also addGlassBackdropScopeto each route — see Backdrop Isolation- Standard quality for scrollable content — lists, forms, interactive widgets
- Premium quality for fixed surfaces — app bars, bottom bars, and hero sections
- Minimal quality for shader-dense screens — use
GlassQuality.minimalfor background panels and list cards to fire zero custom shader invocations during scroll, then keepstandardorpremiumonly on the focal element - Accessibility fallbacks are zero-cost — when Reduce Transparency is active, the glass shader is bypassed entirely;
BackdropFilterblur runs in Flutter's own paint layer with no custom shader overhead
Automatic Quality Adaptation (experimental) #
📊 Help us tune the thresholds — takes 2 minutes #
GlassAdaptiveScopeis@experimentalbecause its Phase 2 timing thresholds are based on limited community data, not yet validated across the full Android device landscape. Current defaults (v0.10.2):
P75 warmup Quality assigned < 20 ms premium(based on 1 report — please share yours)20–28 ms standard(provisional — no real-device data yet)> 28 ms minimalIf you use
adaptiveQuality: true, please post your results to our Threshold Calibration Discussion with the snippet below. Every report directly informs the threshold calibration and gets us closer to removing@experimental. Thank you 🙏// Add to your GlassAdaptiveScopeConfig while testing — remove before shipping: // Option A: zero-wiring (recommended for quick reports) GlassAdaptiveScopeConfig( debugLogDiagnostics: true, // prints to console in debug builds only ) // Option B: custom handler for analytics GlassAdaptiveScopeConfig( onDiagnostic: (d) { // d.reason, d.p75Ms, d.p95Ms, d.framesMeasured, d.phase are all set debugPrint('📊 ${d.from.name} → ${d.to.name} | reason: ${d.reason.name} | P75: ${d.p75Ms?.toStringAsFixed(1)}ms'); }, )
GlassAdaptiveScope (enabled via wrap(adaptiveQuality: true)) automatically
benchmarks the device at startup and adjusts quality in real time:
// Minimal — let the library decide the best quality for the device:
runApp(LiquidGlassWidgets.wrap(child: const MyApp(), adaptiveQuality: true));
// Per-screen — fine-grained control on specific routes:
GlassAdaptiveScope(
initialQuality: GlassQuality.standard, // conservative start
allowStepUp: true,
// Android calibration — raise if your device is incorrectly demoted to standard.
// Post your P75 + device model to the Threshold Calibration Discussion!
// warmupPremiumThresholdMs: 24.0, // default 20.0
// warmupStandardThresholdMs: 32.0, // default 28.0
child: Scaffold(...),
)
Eliminating repeat warmup jank (recommended for production)
On the first launch, GlassAdaptiveScope runs a ~3-second warm-up benchmark
to measure real raster performance. On a Pixel 4a, this benchmark observes slow
frames and steps down to minimal. Without persistence, this happens on every
cold start — the user sees 3 seconds of degraded quality every time they open
the app.
Within a single app process, the library caches the settled quality automatically. If the scope is disposed and remounted (e.g. navigating away and back to the root), Phase 2 is not re-run — no extra code required.
Across cold starts, use onQualityChanged + initialQuality with your
preferred storage mechanism:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Load previously settled quality — avoids warmup jank on repeat launches.
final prefs = await SharedPreferences.getInstance();
final saved = prefs.getString('glass_quality');
final initial = saved != null
? GlassQuality.values.byName(saved) // Dart 2.15+ built-in
: null; // null = run Phase 2 on first launch, then persist
await LiquidGlassWidgets.initialize();
runApp(LiquidGlassWidgets.wrap(
child: const MyApp(),
adaptiveQuality: true,
adaptiveConfig: GlassAdaptiveScopeConfig(
initialQuality: initial, // restore immediately — no warmup window
allowStepUp: true, // allow recovery after thermal throttle
onQualityChanged: (_, to) => // persist whenever quality settles
prefs.setString('glass_quality', to.name),
),
));
}
On first launch: initial is null → Phase 2 runs → quality settles → persisted.
On every subsequent launch: initial is non-null → Phase 2 skipped → no jank.
GPU Budget Monitoring #
GlassPerformanceMonitor watches raster frame durations while GlassQuality.premium surfaces are active. When frames exceed the GPU budget for 60 consecutive frames it emits a single FlutterError with actionable guidance — which widget to change, which quality tier to try, and why.
Zero production overhead — automatically disabled in release builds. Enabled by default in debug/profile via LiquidGlassWidgets.initialize():
// Default — auto-enabled in debug/profile, zero-cost in release
await LiquidGlassWidgets.initialize();
// Opt out entirely
await LiquidGlassWidgets.initialize(enablePerformanceMonitor: false);
// Custom thresholds
GlassPerformanceMonitor.rasterBudget = const Duration(microseconds: 8333); // 120 fps
GlassPerformanceMonitor.sustainedFrameThreshold = 120;
Backdrop Isolation — Preventing Ghost Artifacts #
LiquidGlassWidgets.wrap() installs one root BackdropGroup that all glass
surfaces share for GPU backdrop captures. When navigating between screens, the
previous screen's backdrop texture stays bound for 1–2 frames — causing the old
page's content to briefly bleed through glass on the new screen.
Fix: wrap each screen in GlassBackdropScope.
class MyNewPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GlassBackdropScope( // ← forces a fresh capture on mount
child: Scaffold(
appBar: GlassAppBar(title: const Text('New Page')),
body: ...,
bottomNavigationBar: GlassBottomBar(...),
),
);
}
}
GlassBackdropScope creates a child BackdropGroup scoped to that screen. The
moment it mounts, it captures a fresh GPU backdrop — no memory of the previous
page's content.
Rule of thumb: place a
GlassBackdropScopeat the top of every route or screen that hosts glass surfaces. Think of it like aRepaintBoundaryfor backdrop textures.
Why adaptiveQuality tabs don't ghost:
When switching tabs within the same screen, all glass surfaces share the same
GlassBackdropScope which is refreshed correctly during animation.
The ghost appears only when crossing a Navigator route boundary or an
AnimatedSwitcher that replaces the whole screen widget.
Custom Refraction for Interactive Indicators #
On Skia and Web, interactive widgets like GlassSegmentedControl can display
true liquid glass refraction. Use GlassRefractionSource to mark the capture
surface (or use the LiquidGlassScope.stack() shorthand for the common
wallpaper-behind-content pattern):
// Shorthand — wallpaper behind your Scaffold:
LiquidGlassScope.stack(
background: Image.asset('assets/wallpaper.jpg', fit: BoxFit.cover),
content: Scaffold(
body: Center(
child: GlassSegmentedControl(
segments: const ['Option A', 'Option B', 'Option C'],
selectedIndex: 0,
onSegmentSelected: (i) {},
quality: GlassQuality.standard,
),
),
),
)
// Manual — granular control over which surface is sampled:
LiquidGlassScope(
child: Stack(
children: [
Positioned.fill(
child: GlassRefractionSource(
child: Image.asset('assets/wallpaper.jpg'),
),
),
Center(child: GlassSegmentedControl(...)),
],
),
)
On Impeller, GlassQuality.premium uses the native scene graph — no
LiquidGlassScope needed.
Migration note (0.7.0):
LiquidGlassBackgroundwas renamed toGlassRefractionSource. The old name still compiles (deprecated typedef) and will be removed in 1.0.0.
| When | Recommendation |
|---|---|
| Skia / Web | LiquidGlassScope.stack with GlassQuality.standard |
| iOS / macOS (Impeller) | GlassQuality.premium — native scene graph |
| Multiple isolated sections | Separate LiquidGlassScope per section |
Gyroscope Lighting #
GlassMotionScope drives the specular highlight angle from any Stream<double>, including a device gyroscope via sensors_plus:
GlassMotionScope(
stream: gyroscopeEvents.map((e) => e.y * 0.5),
child: Scaffold(
appBar: GlassAppBar(title: const Text('My App')),
body: ...,
),
)
No new dependencies required — connect any stream source (scroll position, mouse, gyroscope).
Accessibility #
Every glass widget in this package respects the user's system accessibility preferences automatically — no setup required.
| System Setting | Effect on glass widgets |
|---|---|
| Reduce Motion (iOS/macOS/Android) | All spring/jelly animations snap instantly to their target |
| Reduce Transparency / High Contrast | Glass shader replaced with a plain frosted BackdropFilter panel — zero GPU shader cost |
No setup needed #
Just ship your app. If the user has Reduce Motion on, your widgets snap. If they have Reduce Transparency on, they get a solid frosted fallback. Nothing to configure.
Optional: GlassAccessibilityScope #
Place GlassAccessibilityScope in your tree to override system defaults — useful for testing, showcases, or per-subtree customisation:
// In your app (optional — place inside MaterialApp.builder for full coverage)
MaterialApp(
builder: (context, child) => GlassAccessibilityScope(
child: child!, // reads system flags automatically
),
)
// Force a specific state (e.g. demo frosted fallback in a settings screen)
GlassAccessibilityScope(
reduceTransparency: true,
child: GlassSettingsPreview(),
)
GlassAccessibilityScope always wins over the system flag — it's the highest-priority override.
Opting out globally #
For experiences where full glass fidelity is intentional (games, creative tools):
// 0.10.0+: child is a required named parameter
runApp(LiquidGlassWidgets.wrap(
child: const MyApp(),
respectSystemAccessibility: false,
));
This disables only the automatic system-flag bridge. An explicit GlassAccessibilityScope in the widget tree still works regardless.
Priority order (highest wins) #
GlassAccessibilityScopein the widget tree — explicit developer override- System
MediaQueryflags — automatic, respects user's OS setting wrap(respectSystemAccessibility: false)— disables (2) globally
Architecture #
On Impeller, every GlassQuality.premium surface uses a two-pass pipeline:
- Blur pass —
BackdropFilterLayer(ImageFilter.blur), clipped to the exact widget shape. Shared across all surfaces inside aGlassBackdropScope(injected automatically byLiquidGlassWidgets.wrap()). - Shader pass —
BackdropFilterLayer(ImageFilter.shader)— refraction, edge lighting, glass tint, and chromatic aberration.
On Skia/Web, lightweight_glass.frag runs as a single pass with no backdrop capture.
Content-Adaptive Glass Strength (0.7.0) #
Both render paths automatically adapt glass strength to background brightness:
- Dark backgrounds → richer, more opaque glass (1.2× strength, brighter Fresnel rim)
- Light backgrounds → subtler, more translucent glass (0.8× strength)
On Impeller, backdrop luminance is sampled directly from the refracted texture (zero extra reads).
On Skia/Web, MediaQuery.platformBrightnessOf provides a lightweight proxy.
Testing #
# All tests
flutter test
# Exclude golden tests
flutter test --exclude-tags golden
# macOS golden tests (require Impeller)
flutter test --tags golden
Dependencies #
Zero third-party runtime dependencies beyond the Flutter SDK.
The glass rendering pipeline builds on the open-source work of whynotmake-it. Their liquid_glass_renderer (MIT) has been vendored and extended with bug fixes, performance improvements, and shader optimisations.
Contributing #
Contributions are welcome. For major changes, open an issue first to discuss your proposal.
License #
MIT — see the LICENSE file for details.
Credits #
Special thanks to the whynotmake-it team for their liquid_glass_renderer (MIT), whose shader pipeline, texture capture, and chromatic aberration work forms the foundation of the rendering engine in this library.