adaptive_liquid_glass 0.1.0-dev.2 copy "adaptive_liquid_glass: ^0.1.0-dev.2" to clipboard
adaptive_liquid_glass: ^0.1.0-dev.2 copied to clipboard

iOS 26 Liquid Glass for Flutter, measured against SwiftUI, with Material 3 counterparts on Android behind one API.

adaptive_liquid_glass #

iOS 26 Liquid Glass for Flutter, measured against SwiftUI, with Material 3 counterparts on Android, behind one API. App code never branches on platform.

  • iOS 26+: SwiftUI's own Liquid Glass, pixel-identical to a SwiftUI app.
  • Older iOS: a fragment-shader glass tuned to SwiftUI's.
  • Android: a Material 3 surface (colours from your Theme, ink ripple for interactive glass). No glass code runs.
  • RTL-safe: directional insets throughout; the light angle is not mirrored, matching iOS.

Fidelity #

The shipped glass constants are certified against Apple's SwiftUI .glassEffect on the reference simulator (iPhone 17 Pro, iOS 26.4): 46/75 scenes pass the official bars (SSIM ≥ 0.97, CIEDE2000 ΔE ≤ 2.0), median SSIM 0.9828 / median ΔE 1.09, min SSIM 0.9532, 0 scenes below SSIM 0.95 on the 75 in-set scenes (a 48-scene held-out set the fit never saw scores 18/48, min 0.9470). Model↔Flutter parity is 75/75. The full per-scene record, diagnosis and known residuals: docs/superpowers/notes/fidelity-status.md.

To re-run, follow tool/fidelity/README.md: capture both renderers with tool/fidelity/capture.sh build/fidelity/<run> (setup and options documented there), then score with tool/fidelity/.venv/bin/python tool/fidelity/compare.py build/fidelity/<run>.

Quick start #

Install #

The package is not on pub.dev yet (coming soon). Depend on it from git:

dependencies:
  adaptive_liquid_glass:
    git:
      url: https://github.com/aymanomara/adaptive_liquid_glass

After the pub.dev release, this will be:

dependencies:
  adaptive_liquid_glass: ^0.1.0  # after release
import 'package:adaptive_liquid_glass/adaptive_liquid_glass.dart';

No setup is needed: no theme, no initialisation call, no platform checks.

One line #

const Text('Hello').glassEffect(padding: const EdgeInsets.all(12));

glassEffect mirrors SwiftUI's .glassEffect(_:in:) and returns a LiquidGlass widget. Text and icons on glass get a readable colour by default, like SwiftUI's vibrant labels; colours you set yourself still win.

The full widget takes the same options and a few more:

LiquidGlass(
  glass: Glass.regular.tint(Colors.blue),   // default: Glass.regular
  shape: const GlassShape.rect(20),         // default: capsule
  padding: const EdgeInsetsDirectional.all(16),
  onPressed: () {},                         // makes it a button
  child: const Text('Continue'),
)

With onPressed the glass is a button: it gets button semantics, takes focus, answers Enter and Space, and its glass turns interactive (the press stretch and glow on iOS, the ripple on Android). Pass Glass.regular.interactive(false) to keep the tap without the press visuals.

A group of buttons #

Glass next to glass belongs in one GlassGroup, SwiftUI's GlassEffectContainer. Members are drawn in one pass, sample the same backdrop, and can blend and morph into each other.

GlassGroup(
  spacing: 16,
  child: Row(
    mainAxisSize: MainAxisSize.min,
    spacing: 12,
    children: [
      LiquidGlass(
        shape: const GlassShape.circle(),
        padding: const EdgeInsets.all(14),
        onPressed: () {},
        child: const Icon(Icons.edit),
      ),
      LiquidGlass(
        shape: const GlassShape.circle(),
        padding: const EdgeInsets.all(14),
        onPressed: () {},
        child: const Icon(Icons.share),
      ),
    ],
  ),
)

spacing is how close two shapes must be to flow together. The default, 0, merges only shapes that touch, so a row of buttons stays a row of separate buttons. Set it to at least the gap between them (here 12) for SwiftUI's liquid blending.

Theming and native mode #

A LiquidGlassTheme is optional; without one the defaults apply.

LiquidGlassTheme(
  data: const LiquidGlassThemeData(
    defaultGlass: Glass.clear,  // glass for widgets that do not pick one
  ),
  child: MaterialApp(home: const HomePage()),
)

On iOS 26 and later, glass is SwiftUI's own Liquid Glass: each GlassGroup hosts one GlassEffectContainer with a .glassEffect view per member in a platform view behind its content, so it matches a SwiftUI app pixel for pixel (including dark mode, tint, merging and Reduce Transparency, which SwiftUI handles itself). There is no flag: the iOS version is read at startup, so the first frame already uses it. Older iOS gets the shader. To force a path for one subtree, pass mode: to GlassGroup or LiquidGlass (GlassRenderMode.auto, shader, native or material); set defaultMode: GlassRenderMode.shader in the theme to opt out of native glass app-wide.

The shader (older iOS, or mode: shader) loads on first use. Until it is ready (usually a frame or two), glass draws as a plain blur and then switches over in place. To skip that, optionally load it before the first frame:

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await LiquidGlass.precache();
  runApp(const MyApp());
}

Android #

On Android, and any platform other than iOS, auto renders Material 3:

Glass Material 3
Glass.regular surfaceContainerHigh, elevation 1
Glass.clear surfaceContainerLow at 85 % opacity
.tint(color) primaryContainer of ColorScheme.fromSeed(color)
.interactive() / onPressed InkWell ripple
GlassGroup layout only (no merging)

Text and icons on the surface use its matching "on" colour (onSurface, or the tint's onPrimaryContainer).

Accessibility #

  • Reduce Transparency (iOS) turns glass into an opaque surface, as iOS does (on iOS 26+ SwiftUI's glass does this itself).
  • Increase Contrast strengthens the rim and reduces lensing on the shader path; on iOS 26+ it is whatever SwiftUI's own glass does.
  • Reduce Motion keeps the press glow and drops the stretch and bounce.
  • Glass is decoration: semantics come from the child, plus button semantics when onPressed is set.

Cookbook #

Every recipe is in the example app's Gallery (example/lib/gallery.dart).

Glass button #

LiquidGlass(
  onPressed: save,
  padding: const EdgeInsetsDirectional.symmetric(horizontal: 24, vertical: 14),
  child: const Text('Save'),
)

Card with padding #

const LiquidGlass(
  shape: GlassShape.rect(24),
  padding: EdgeInsetsDirectional.all(20),
  child: Column(
    mainAxisSize: MainAxisSize.min,
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      Text('Liquid Glass', style: TextStyle(fontSize: 22)),
      Text('Text on glass picks a readable colour.'),
    ],
  ),
)

Nested glass with GlassShape.concentric() follows the card's corners, inset by the distance between them.

Morphing pair with glassId #

Give glass an identity and it morphs in and out of its neighbours as it appears and disappears, like glassEffectID:

GlassGroup(
  spacing: 20,
  child: Row(
    mainAxisSize: MainAxisSize.min,
    spacing: 12,
    children: [
      LiquidGlass(
        glassId: 'toggle',
        shape: const GlassShape.circle(),
        padding: const EdgeInsets.all(14),
        onPressed: () => setState(() => expanded = !expanded),
        child: Icon(expanded ? Icons.close : Icons.add),
      ),
      if (expanded)
        const Icon(Icons.favorite).glassEffect(
          glassId: 'extra',
          shape: const GlassShape.circle(),
          padding: const EdgeInsets.all(14),
        ),
    ],
  ),
)

Merged union #

Members with the same unionId are drawn as one shape however far apart they are, like glassEffectUnion:

GlassGroup(
  child: Row(
    mainAxisSize: MainAxisSize.min,
    spacing: 24,
    children: [
      for (final icon in [Icons.edit, Icons.share, Icons.delete])
        Icon(icon).glassEffect(
          shape: const GlassShape.circle(),
          unionId: 'tools',
          padding: const EdgeInsets.all(12),
        ),
    ],
  ),
)

Clear glass over media #

Glass.clear is the highly transparent variant for photos and video:

Stack(
  alignment: Alignment.center,
  children: [
    Image.asset('cover.jpg'),
    const Icon(Icons.play_arrow, size: 36).glassEffect(
      glass: Glass.clear.interactive(),
      shape: const GlassShape.circle(),
      padding: const EdgeInsets.all(16),
    ),
  ],
)

Tinted glass #

LiquidGlass(
  glass: Glass.regular.tint(Colors.blue),
  onPressed: () {},
  padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 14),
  child: const Text('Tinted'),
)

Adaptive foreground over busy content #

By default the label colour follows the platform brightness. To follow the content actually behind the glass, wrap that content in a GlassBackdropSource; each group then samples it (a small GPU readback every 250 ms) and GlassForeground.labelColorOf(context) switches between dark and light labels.

Stack(
  children: [
    GlassBackdropSource(child: photoGrid),
    Align(
      alignment: AlignmentDirectional.bottomCenter,
      child: const Text('Now playing').glassEffect(),
    ),
  ],
)

Fidelity #

The shader is fitted against SwiftUI's own Liquid Glass, rendered on the reference simulator (iPhone 17 Pro, iOS 26.4), over 75 scenes (regular, clear and tinted glass; capsules, circles and rounded rectangles; photo, text and gradient backgrounds; light and dark; merged groups):

path result
Shader (iOS < 26, or mode: shader) every scene at SSIM ≥ 0.95 (minimum 0.9532); 46/75 pass the strict bars (SSIM ≥ 0.97 and ΔE ≤ 2.0); median SSIM 0.983, median ΔE 1.09
Native (iOS 26+, the default) 75/75 at parity with SwiftUI (minimum SSIM 0.9996, maximum ΔE 0.05)

Press and morph motion uses SwiftUI's measured springs (press-in, release and .bouncy morph) and a size-dependent press growth. The harness lives in tool/fidelity/ (see its README).

Known limitations #

  • Native mode, runtime light→dark flip: on iOS 26, 71 of 72 measured brightness flips at runtime match SwiftUI. One does not: a regular glass capsule over a photo, flipped from light to dark, keeps part of Apple's previous look (SSIM 0.84 against a dark launch, which matches). Apple's glass appears to keep state from glass drawn earlier in the process; it is not fixed by rebuilding the glass.
  • Native interactive glass: pressing native glass gets this package's stretch and growth, but not SwiftUI's own touch glow.
  • Shader glow: the shader's touch glow is brighter than SwiftUI's.
4
likes
0
points
326
downloads

Publisher

unverified uploader

Weekly Downloads

iOS 26 Liquid Glass for Flutter, measured against SwiftUI, with Material 3 counterparts on Android behind one API.

Repository (GitHub)
View/report issues

Topics

#liquid-glass #cupertino #material #ui #shader

License

unknown (license)

Dependencies

flutter

More

Packages that depend on adaptive_liquid_glass

Packages that implement adaptive_liquid_glass