sdegenaar
liquid_glass_widgets
Dart

Flutter UI kit implementing Apple's iOS 26 Liquid Glass design language - a comprehensive glass widget library with real shader-based blur, physics-driven jelly animations, and dynamic lighting. Works on every platform out of the box.

Last updated Aug 9, 2026
463
Stars
66
Forks
5
Issues
+1
Stars/day
Attention Score
80
Language breakdown
Dart 97.2%
GLSL 2.4%
Shell 0.4%
β–Έ Files click to expand
README

Liquid Glass Widgets

Bring Apple's iOS 26 Liquid Glass to your Flutter app β€” real shader-based blur, physics-driven jelly animations, and dynamic lighting across every platform.

pub package pub points likes CI codecov License: MIT


Liquid Glass widgets demo β€” Apple Music and Podcasts


Liquid Glass widgets demo β€” interactive controls and navigation

Installation

dependencies:
  liquidglasswidgets: ^0.29.4
flutter pub get
Flutter version requirement: Requires Flutter β‰₯ 3.41.0 (Dart β‰₯ 3.5.0).
Recommended: Flutter 3.41+ for the best Impeller rendering quality.
This package uses cutting-edge shader APIs that improve significantly with each Flutter release.

Quick Start

Two steps β€” that's the entire setup:

Step 1. Call initialize() in main() to pre-warm shaders.

Step 2. Wrap your app with LiquidGlassWidgets.wrap():

import 'package:flutter/material.dart';
import 'package:liquidglasswidgets/liquidglasswidgets.dart';

void main() async { WidgetsFlutterBinding.ensureInitialized(); await LiquidGlassWidgets.initialize();

runApp(LiquidGlassWidgets.wrap(child: const MyApp())); }

That's it. Then use GlassScaffold on each screen β€” it handles background, status bar, z-ordering, and edge fading automatically:

GlassScaffold(
  background: Image.asset('assets/wallpaper.jpg', fit: BoxFit.cover),
  statusBarStyle: GlassStatusBarStyle.auto,
  appBar: GlassAppBar(title: const Text('My App')),
  body: Center(child: GlassCard(child: Text('Hello, Glass!'))),
)
Why GlassScaffold? Glass effects refract and blur against whatever is behind them. Without a controlled background, glass surfaces can appear flat, incorrectly tinted, or invisible. GlassScaffold wires up the background source, glass rendering layer, and bar isolation automatically β€” one widget instead of five.
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.

Choose the right widget

The package is centred around navigation chrome β€” GlassScaffold with GlassAppBar and GlassTabBar is the primary pattern and where the iOS 26 liquid glass effect is most impactful.

| Scenario | Widget to use | |---|---| | Screen with app bar and/or tab bar | GlassScaffold β€” the primary pattern | | Custom layout without standard scaffold structure | GlassPage β€” lower-level building block | | Standalone glass card or panel in an existing layout | GlassCard / GlassContainer β€” opt-in, not the core pattern | | Localised group of glass elements in an existing layout | AdaptiveLiquidGlassLayer β€” scope a layer to a region; grouped cards share its settings |

GlassContainer / GlassCard are fully supported for localised glass UI (floating panels, settings cards, etc.) but most screens should start with GlassScaffold.

Optional: quality & theming

For production apps, pass adaptiveQuality and/or theme to wrap() at the same call site:

runApp(LiquidGlassWidgets.wrap(
  child: const MyApp(),
  adaptiveQuality: true,          // auto-benchmarks device, degrades gracefully
  theme: GlassThemeData.simple(   // optional app-wide glass defaults
    blur: 10,
    thickness: 30,
    quality: GlassQuality.standard,
  ),
));

Both parameters are optional β€” omit them and the library uses sensible defaults.

Features

  • Comprehensive glass widget library β€” containers, interactive controls, inputs, feedback, overlays, and navigation surfaces (see Widget Categories)
  • Liquid Morph Engine β€” a standalone physics system powering iOS 26-style liquid morphing. GlassMenu is the first consumer; future widgets will use the same engine for consistent liquid transitions. See docs/LIQUIDMORPH_ENGINE.md
  • 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) β€” GlassAdaptiveScope benchmarks the device at startup and adjusts quality in real time: minimal on slow hardware, standard on mid-range, premium on fast devices. Degrades on thermal throttle, recovers when cool
  • Minimal dependencies β€” only equatable, flutter_shaders, and logging beyond the Flutter SDK
  • One-line setup β€” LiquidGlassWidgets.wrap(child: myApp) handles accessibility bridging, adaptive quality, and global theming; use GlassScaffold per screen for automatic backdrop isolation, z-ordering, edge fading, and status bar styling
  • Content-aware brightness β€” glass bars automatically flip between light and dark icons/labels based on the content scrolling behind them. One flag on GlassScaffold, matches iOS 26 behaviour
  • Gyroscope lighting β€” GlassMotionScope drives specular highlights from any Stream<double>
  • WCAG-compliant by default β€” Reduce Motion and Reduce Transparency are respected automatically; no setup required
  • Full keyboard & screen reader support β€” every interactive widget supports Tab navigation, Space/Enter activation, and VoiceOver/TalkBack semantics out of the box; focus is visualised with an iOS 26-style outset ring
  • Full RTL (Right-to-Left) support β€” layouts, drag directions, tab ordering, and physics auto-reverse for Arabic, Hebrew, and Persian

Glass vs Content β€” Design Philosophy

In iOS 26, glass is reserved for the navigation and control layer β€” the floating UI that sits above your app's content. Content areas (lists, cards, article tiles) stay opaque.

| βœ… Use glass for | ❌ Keep opaque | |---|---| | Navigation bars, tab bars, toolbars | List cells, table rows | | Floating action buttons | Full-screen backgrounds | | Sheets, popovers, menus | Scrollable content cards | | Toggles, sliders, segmented controls | Article tiles, media players |

Typical screen composition:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   GlassAppBar (glass)    β”‚  ← Navigation chrome
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                          β”‚
β”‚   Opaque content area    β”‚  ← Standard Flutter widgets
β”‚   (ListView, Cards, etc) β”‚
β”‚                          β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  GlassBottomBar (glass)  β”‚  ← Navigation chrome
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Building a Settings screen? Use GlassScaffold + GlassAppBar for navigation chrome, and CupertinoListTile or standard Flutter containers for the rows. Use GlassGroupedSection when you want glass-styled grouped rows.

Glass Composition Rule: Glass is a Platter, Not a Wrapper

GlassCard, GlassContainer, and GlassGroupedSection are base surfaces β€” they sit beneath your content. They are not generic styling wrappers for other glass controls.

| βœ… Place inside GlassCard / GlassContainer | ❌ Do not place inside GlassCard / GlassContainer | |---|---| | Text, Icon, ListTile, CupertinoListTile | GlassSegmentedControl, GlassSlider, GlassSwitch | | GlassListTile, GlassDivider | GlassButton, GlassChip, GlassIconButton | | Standard Flutter form widgets | Any other refractive glass widget |

Why? GlassContainer sets avoidsRefraction: true on its children so nested glass cannot refract through the outer layer β€” the inner effect degrades by design. On Impeller with useOwnLayer: true, the container's own-layer clip also cuts jelly-physics overshots from interactive indicators (segmented control pill, slider thumb) during animations.

Interactive glass controls already provide their own surface appearance via backgroundColor and indicatorColor β€” no outer container is needed for the track or background.

Widget Categories

Containers

GlassCard Β· GlassContainer\* Β· GlassDivider Β· GlassGroupedSection Β· GlassListTile Β· GlassStepper

\* GlassContainer is a low-level building block for custom glass surfaces. Most apps should use GlassCard or GlassGroupedSection instead.

Interactive

GlassButton Β· GlassIconButton Β· GlassChip Β· GlassSwitch Β· GlassSlider Β· GlassSegmentedControl Β· GlassPullDownButton Β· GlassButtonGroup Β· GlassBadge Β· GlassPageControl

Input

GlassTextField Β· GlassTextArea Β· GlassPasswordField Β· GlassSearchBar Β· GlassPicker Β· GlassFormField

Feedback

GlassProgressIndicator Β· GlassToast

Overlays

GlassDialog Β· GlassSheet Β· GlassModalSheet Β· showGlassActionSheet Β· GlassMenu Β· GlassMenuItem Β· GlassMenuDivider Β· GlassMenuLabel Β· GlassPopover

Surfaces

GlassScaffold Β· GlassAppBar Β· GlassTabBar (.bottom / .inline / .searchable) Β· GlassToolbar Β· GlassContentAwareScope Β· GlassContentAwareContent Β· GlassContentAwareBrightness

Theming

Pass a theme: to LiquidGlassWidgets.wrap() to set your app-wide defaults β€” every glass widget inherits them automatically, no per-widget configuration needed:

runApp(LiquidGlassWidgets.wrap(
  child: const MyApp(),
  theme: GlassThemeData(
    light: GlassThemeVariant(
      settings: GlassThemeSettings(thickness: 30, blur: 6),
      quality: GlassQuality.standard,
    ),
    dark: GlassThemeVariant(
      settings: GlassThemeSettings(thickness: 40, blur: 8),
      quality: GlassQuality.standard,
    ),
  ),
));

For a quick single-quality theme, use the GlassThemeData.simple shorthand:

runApp(LiquidGlassWidgets.wrap(
  child: const MyApp(),
  theme: GlassThemeData.simple(
    blur: 10,
    thickness: 30,
    quality: GlassQuality.standard,
  ),
));
GlassThemeSettings vs LiquidGlassSettings: Use GlassThemeSettings inside GlassThemeVariant. It accepts the same parameters but all are nullable β€” only fields you explicitly set are applied; everything else inherits from each widget's own defaults. LiquidGlassSettings is the full settings type used on individual widgets.

Three-level override hierarchy (highest wins):

  • Widget settings parameter β€” explicit, widget-level override
  • GlassPage(themeOverride: ...) β€” per-screen override for special pages (onboarding, paywalls)
  • GlassTheme / wrap(theme:...) β€” app-wide defaults
Access the current theme programmatically:
final variant = GlassThemeData.of(context).variantFor(context);

Per-subtree theming

For advanced use cases where you need different glass styles within a single screen, place a GlassTheme widget anywhere in your tree:

GlassTheme(
  data: GlassThemeData.simple(blur: 4, quality: GlassQuality.minimal),
  child: MyListSection(), // list cards get minimal quality
)

Glow Colors

GlassGlowColors controls the interaction glow emitted by surfaces like GlassBottomBar and GlassSearchableBottomBar:

GlassThemeVariant(
  glowColors: GlassGlowColors(
    primary: Colors.blue,
    glowBlurRadius: 12,
    glowSpreadRadius: 0.2,
    glowOpacity: 0.8,
  ),
)

Platform Support

| Platform | Renderer | Notes | |---|---|---| | iOS | Impeller (Metal) | Full shader pipeline, chromatic aberration | | Android (Vulkan) | Impeller (Vulkan) | Full shader pipeline, chromatic aberration | | Android (GLES fallback) | Impeller (GLES) | Full shader pipeline; runtime shader compilation β€” initialize() handles this automatically. See Android GLES note | | 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. LiquidGlassWidgets.initialize() performs an Android-specific GPU warm-up before runApp to prevent startup ANRs on GLES devices.

Android GLES note

A portion of the Android fleet does not support Vulkan and runs Impeller GLES instead. Unlike Vulkan (which uses precompiled SPIR-V), GLES compiles GLSL shader source at runtime on the raster thread. On mid-range SoCs this can take 100–800Β ms. If this coincides with Android’s surface setup (FlutterJNI.nativeSurfaceChanged), Android may declare an ANR ("Input dispatching timed out").

LiquidGlassWidgets.initialize() mitigates this automatically: it draws a 1Γ—1 off-screen frame using the premium glass shaders before runApp, forcing GLES compilation behind the native splash screen where it cannot race with surface setup.

Affected hardware includes many budget and older mid-range devices (MediaTek Helio G-series, Qualcomm Snapdragon 4xx/6xx, pre-AndroidΒ 9 devices). Flagship and recent mid-range devices with full Vulkan support are unaffected β€” the warm-up completes in ~1–2Β ms on Vulkan.

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.

GlassCard(
  quality: GlassQuality.premium,
  child: const Text('Static hero section'),
)
Use Premium only for static, non-scrolling surfaces (hero sections, feature cards). It may not render correctly inside ListView or CustomScrollView on Impeller. GlassScaffold automatically promotes app bars and bottom bars to premium quality via GlassIsolationScope.

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.isShaderFilterSupported is false
  • GPU budget management β€” use minimal for background panels and list cards while keeping standard or premium on the focal element. A screen with 15 glass list cards running minimal fires zero shader invocations during scroll
Theme shorthand: GlassThemeVariant.minimal applies minimal quality globally via GlassThemeData.

GlassScaffold

GlassScaffold is the recommended way to build any screen that uses glass surfaces. It replaces the manual assembly of GlassPage + Scaffold + GlassScrollEdgeEffect + Stack with a single widget:

GlassScaffold(
  background: Image.asset('assets/wallpaper.jpg', fit: BoxFit.cover),
  statusBarStyle: GlassStatusBarStyle.light,
  appBar: GlassAppBar(
    title: const Text('Messages'),
    trailing: GlassButton(
      icon: const Icon(CupertinoIcons.compose),
      onTap: () {},
    ),
  ),
  bottomBar: GlassTabBar.bottom(
    selectedIndex: 0,
    onTabSelected: (_) {},
    tabs: const [
      GlassTab(icon: Icon(Icons.home), label: 'Home'),
      GlassTab(icon: Icon(Icons.search), label: 'Search'),
    ],
  ),
  body: CustomScrollView(
    slivers: [...],
  ),
)

| What it handles | Without GlassScaffold | |---|---| | Background + glass layer | Must wrap in GlassPage + set scaffoldBackgroundColor: transparent | | Z-ordering (bars above body) | Must build a manual Stack with correct paint order | | Edge fading | Must add GlassScrollEdgeEffect and calculate fade heights | | Safe-area padding | Must calculate top/bottom padding for app bar and bottom bar | | Bar isolation | Must wrap bars in GlassIsolationScope manually | | Status bar icons | Must call SystemChrome.setSystemUIOverlayStyle and restore it |

See example/lib/demos/navbarpatterns_demo.dart for complete GlassScaffold usage patterns.

Content-Aware Brightness

Glass bars automatically adapt their icon and label colors to match the content scrolling behind them β€” light icons over dark content, dark icons over light content β€” with a smooth cross-fade transition. One flag on GlassScaffold, one on the bar:

GlassScaffold(
  contentAwareBrightness: true,
  bottomBar: GlassTabBar.bottom(
    adaptiveBrightness: true,
    onBrightnessChanged: (b) => debugPrint('Bar is now: $b'),
    tabs: [...],
    selectedIndex: _index,
    onTabSelected: (i) => setState(() => _index = i),
  ),
  body: CustomScrollView(
    slivers: [...], // content scrolls underneath the bar
  ),
)

GlassScaffold.contentAwareBrightness handles all the wiring β€” it wraps the body in GlassContentAwareContent and the layout in GlassContentAwareScope automatically. The bar uses WCAG contrast ratios with dual-threshold hysteresis to prevent flickering on borderline content.

For custom layouts without GlassScaffold, use the standalone widgets directly:

GlassContentAwareScope(
  child: Scaffold(
    extendBody: true,
    body: GlassContentAwareContent(
      child: ListView(...),
    ),
    bottomNavigationBar: GlassTabBar.bottom(
      adaptiveBrightness: true,
      ...
    ),
  ),
)
See example/lib/demos/contentawarebrightness_demo.dart for a focused showcase.

GlassPage

GlassPage is the lower-level building block that GlassScaffold uses internally. Use it directly when you need full manual control over your layout β€” custom Stack ordering, non-standard bar placements, or screens without a traditional scaffold structure.

For most apps, GlassScaffold is simpler β€” it handles background, bars, edge fading, and isolation automatically. Use GlassPage only when you need to build a custom layout that GlassScaffold doesn't support.

GlassPage eliminates several common setup mistakes in one widget:

// Minimum β€” just wrap your Scaffold, GlassPage handles everything else:
GlassPage(
  child: Scaffold(
    appBar: GlassAppBar(title: const Text('Home')),
    body: MyContent(),
  ),
)

// With a wallpaper: GlassPage( background: Image.asset('assets/wallpaper.jpg', fit: BoxFit.cover), edgeToEdge: true, statusBarStyle: GlassStatusBarStyle.auto, child: Scaffold( appBar: GlassAppBar(title: const Text('Home')), body: MyContent(), ), )

| What it handles | Without GlassPage | |---|---| | Transparent Scaffold | Must set scaffoldBackgroundColor: transparent manually | | Navigation ghosting | Handled automatically β€” each glass layer isolates its own backdrop | | Background scope setup | Must wrap in LiquidGlassScope manually | | Status bar icons | Must call SystemChrome.setSystemUIOverlayStyle and restore it | | Edge-to-edge mode | Must call SystemChrome.setEnabledSystemUIMode and restore it | | Per-screen theme | Must wrap subtree in a local GlassTheme manually |

Parameters

| Parameter | Default | Purpose | |---|---|---| | background | null | Optional wallpaper/background widget. When omitted, Scaffold background is left unchanged | | child | required | Screen content, typically a Scaffold | | enableBackgroundSampling | true when background is set, false otherwise | GPU texture capture for real colour absorption. Set false explicitly to opt out | | statusBarStyle | GlassStatusBarStyle.none | Status bar icon brightness; auto is recommended for wallpaper screens | | edgeToEdge | false | Draw content behind system bars (full immersive) | | themeOverride | null | Per-screen GlassThemeData override for special screens | >

Tip for edgeToEdge on Android: When true, content draws underneath the Android navigation bar. Remember to wrap your Scaffold body in a SafeArea (or use extendBody: true and pad the bottom) so your content isn't hidden behind the system buttons.

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 render
  • LiquidGlassWidgets.wrap() in main.dart β€” installs accessibility bridging and global theming; pass adaptiveQuality: true for automatic per-device quality tuning
  • 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.minimal for background panels and list cards to fire zero custom shader invocations during scroll, then keep standard or premium only on the focal element
  • Accessibility fallbacks are zero-cost β€” when Reduce Transparency is active, the glass shader is bypassed entirely; BackdropFilter blur runs in Flutter's own paint layer with no custom shader overhead

Automatic Quality Adaptation (experimental)

πŸ“Š GlassAdaptiveScope is @experimental β€” its timing thresholds need more real-device data to be finalised. If you use adaptiveQuality: true, please share your device model, Flutter version, and observed P75 ms in our Threshold Calibration Discussion. See docs/ADAPTIVEQUALITY.md for current threshold values and the reporting snippet.

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;

Custom Refraction for Interactive Indicators

On Skia and Web, interactive widgets like GlassSegmentedControl can display true liquid glass refraction from a background image.

Recommended: use GlassPage(background:...) β€” it wires up the refraction source automatically and is the cleanest integration path:

// GlassPage handles LiquidGlassScope + GlassBackgroundSource for you:
GlassPage(
  background: Image.asset('assets/wallpaper.jpg', fit: BoxFit.cover),
  child: Scaffold(
    body: Center(
      child: GlassSegmentedControl(
        segments: const ['Option A', 'Option B', 'Option C'],
        selectedIndex: 0,
        onSegmentSelected: (i) {},
        quality: GlassQuality.standard,
      ),
    ),
  ),
)

Manual alternative β€” LiquidGlassScope:

For advanced scenarios (e.g. isolated sections within a screen, non-GlassPage setups), use LiquidGlassScope directly:

// Shorthand β€” wallpaper behind your Scaffold:
LiquidGlassScope.stack(
  background: Image.asset('assets/wallpaper.jpg', fit: BoxFit.cover),
  content: Scaffold(
    body: Center(child: GlassSegmentedControl(...)),
  ),
)

// Manual β€” granular control over which surface is sampled: LiquidGlassScope( child: Stack( children: [ Positioned.fill( child: GlassBackgroundSource( child: Image.asset('assets/wallpaper.jpg'), ), ), Center(child: GlassSegmentedControl(...)), ], ), )

On Impeller, GlassQuality.premium uses the native scene graph β€” no LiquidGlassScope needed.

| When | Recommendation | |---|---| | Skia / Web (recommended) | GlassPage(background:...) β€” automatic wiring | | Skia / Web (manual) | 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)

  • GlassAccessibilityScope in the widget tree β€” explicit developer override
  • System MediaQuery flags β€” automatic, respects user's OS setting
  • wrap(respectSystemAccessibility: false) β€” disables (2) globally

Architecture

Rendering pipeline

On Impeller, every GlassQuality.premium surface uses a two-pass pipeline:

  • Blur pass β€” BackdropFilterLayer(ImageFilter.blur), clipped to the exact widget shape. Each LiquidGlassLayer manages its own isolated BackdropGroup for GPU capture.
  • 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.

Liquid Morph Engine

A standalone physics and animation system powering iOS 26-style teardrop morphing. It lives in lib/engine/ and is fully decoupled from any specific widget β€” GlassMenu is its first consumer.

Key types: GlassMorphController Β· LiquidMorphState Β· LiquidMorphPhysics Β· MorphPhase Β· MorphSpeed

See docs/LIQUIDMORPH_ENGINE.md for a full integration guide.

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

Minimal runtime dependencies beyond the Flutter SDK: equatable, flutter_shaders, and logging.

The glass rendering pipeline builds on the open-source work of whynotmake-it. Their liquidglassrenderer (MIT) has been vendored and extended with bug fixes, performance improvements, and shader optimisations.

Showcase

Run any demo directly on your device:

| Demo | Command | |------|---------| | Apple Music | cd example && flutter run -t lib/applemusic/applemusic_demo.dart | | Apple Podcasts | cd example && flutter run -t lib/applepodcasts/applepodcasts_demo.dart | | Apple News | cd example && flutter run -t lib/applenews/applenews_demo.dart | | Apple Messages | cd example && flutter run -t lib/applemessages/applemessages_demo.dart | | Widget Showcase | cd example && flutter run | | Wanderlust | cd example/showcase && flutter pub get && flutter run |

Wanderlust β€” Luxury Travel Showcase

A premium app demonstrating liquidglasswidgets in a real-world production context β€” full-bleed imagery, parallax scroll, hero transitions, and a concierge chat interface.

cd example/showcase && flutter pub get && flutter run

Apple Messages Demo β€” iOS 26 Replica

A replica showcasing the Liquid Morph Engine via GlassMenu. Tap the menu or Edit button at the top to see the teardrop open/close physics live.

cd example && flutter pub get && flutter run -t lib/applemessages/applemessages_demo.dart

Component Demos β€” Copy-Pasteable Examples

Focused, self-contained demos β€” one widget, one file, runnable standalone:

| Demo | Run command (from example/) | |---|---| | glassmenudemo.dart β€” all 9 menu alignments | cd example && flutter run -t lib/demos/glassmenudemo.dart | | glasstabbarscrollabledemo.dart β€” scrollable tab bar | cd example && flutter run -t lib/demos/glasstabbarscrollabledemo.dart | | glassmodalsheetdemo.dart β€” peek / half / full states | cd example && flutter run -t lib/demos/glassmodalsheetdemo.dart | | glassbottombardemo.dart β€” magic-lens masking | cd example && flutter run -t lib/demos/glassbottombardemo.dart | | bottombartabwidthdemo.dart β€” tabWidth showcase | cd example && flutter run -t lib/demos/bottombartabwidthdemo.dart | | searchablebardemo.dart β€” searchable bar edge cases | cd example && flutter run -t lib/demos/searchablebardemo.dart | | shapedebugdemo.dart β€” GlassButton shapes | cd example && flutter run -t lib/demos/shapedebugdemo.dart | | qualitycomparisondemo.dart β€” premium & standard quality | cd example && flutter run -t lib/demos/qualitycomparisondemo.dart | | navbarpatternsdemo.dart β€” GlassScaffold layout patterns | cd example && flutter run -t lib/demos/navbarpatternsdemo.dart | | contentawarebrightnessdemo.dart β€” light/dark bar adaptation | cd example && flutter run -t lib/demos/contentawarebrightnessdemo.dart | | indicatorparitydemo.dart β€” all four pill widgets side-by-side | cd example && flutter run -t lib/demos/indicatorparitydemo.dart |

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 liquidglassrenderer (MIT), whose shader pipeline, texture capture, and chromatic aberration work forms the foundation of the rendering engine in this library.

Links

Β© 2026 GitRepoTrend Β· sdegenaar/liquid_glass_widgets Β· Updated daily from GitHub