flutter_liquid_glass_kit
Platform-adaptive Liquid Glass components for Flutter. The package uses native SwiftUI glass on iOS and an optimized Flutter matte-glass renderer elsewhere.
| Platform | Renderer |
|---|---|
| iOS 26+ | Native SwiftUI .glassEffect() |
| iOS 16-25 | Native system-material fallback |
| Android, web, desktop | Flutter blur, tint, highlight, border, and shadow |
Installation
flutter pub add flutter_liquid_glass_kit
import 'package:flutter_liquid_glass_kit/flutter_liquid_glass_kit.dart';
Components
Card
const LiquidGlassCard(
padding: EdgeInsets.all(20),
child: Text('Hello, Glass!'),
)
width and height are optional fixed dimensions. When they are omitted, the
card follows its parent constraints and child size. margin is outside the
glass surface; padding is inside it.
Button
LiquidGlassButton(
onPressed: () {},
isLoading: false,
child: const Text('Continue'),
)
A null onPressed disables the button. isLoading: true also disables taps and
replaces the child with a progress indicator. Use pressScaleFactor and
animationDuration to customize press feedback.
Text field
LiquidGlassTextField(
controller: nameController,
decoration: const InputDecoration(
hintText: 'Name',
prefixIcon: Icon(Icons.person_outline),
),
textInputAction: TextInputAction.next,
autofillHints: const [AutofillHints.name],
onSubmitted: saveName,
)
The editable field remains a Flutter TextField, while its background uses
native SwiftUI glass on iOS and the optimized fallback elsewhere. This keeps
Flutter controllers, focus nodes, formatters, selection, autofill, and keyboard
behavior available without a second native text-input bridge. Provide borders
through decoration when needed; otherwise the glass outline is used.
Navigation bar
LiquidGlassNavBar is a normal layout widget. It is controlled: update
currentIndex from onTap.
Scaffold(
body: pages[currentIndex],
bottomNavigationBar: SafeArea(
minimum: const EdgeInsets.all(16),
child: LiquidGlassNavBar(
currentIndex: currentIndex,
onTap: (index) => setState(() => currentIndex = index),
items: const [
LiquidGlassNavItem(
icon: Icon(Icons.home_outlined),
activeIcon: Icon(Icons.home),
label: 'Home',
iosSystemImage: 'house',
iosSelectedSystemImage: 'house.fill',
),
LiquidGlassNavItem(
icon: Text('S'),
label: 'Search',
iosSystemImage: 'magnifyingglass',
),
],
),
),
)
Use LiquidGlassFloatingNavBar inside a Stack when content should continue
behind the bar:
Stack(
children: [
pages[currentIndex],
LiquidGlassFloatingNavBar(
child: LiquidGlassNavBar(
currentIndex: currentIndex,
onTap: (index) => setState(() => currentIndex = index),
items: items,
),
),
],
)
The floating wrapper applies horizontal padding and includes the bottom safe
area by default. Set respectSafeArea: false only when an ancestor already
handles it.
On the Flutter fallback, users can hold and drag the selection indicator. Icons and labels preview the item under the indicator, but navigation occurs only after release. Cancelling restores the current item. During horizontal dragging, the indicator stretches vertically in proportion to finger speed and relaxes when movement pauses.
Choose a built-in Android indicator animation:
LiquidGlassNavBar(
currentIndex: currentIndex,
onTap: onTap,
androidAnimationStyle: LiquidGlassNavBarAnimationStyle.elastic,
items: items,
)
Available styles are liquid, elastic, pulse, and smooth. The value can
be changed at runtime like any other widget property. For complete control,
provide a geometry resolver:
LiquidGlassNavBar(
currentIndex: currentIndex,
onTap: onTap,
androidAnimationResolver: (state) {
final speed = (state.dragSpeed / 1800).clamp(0.0, 1.0);
return Rect.fromCenter(
center: Offset(state.center, state.dockHeight / 2),
width: state.restingRect.width + 12 * state.holdProgress,
height: state.restingRect.height + 24 * speed,
);
},
items: items,
)
The resolver receives transition progress, hold progress, signed drag velocity, item dimensions, travel distance, and resting geometry. Keep it fast and free of side effects because it runs every animation frame. These options affect the Flutter/Android indicator; native iOS continues using its system animation.
iOS and Android can optionally shrink the bar while a vertical scrollable moves down, then restore it after enough upward movement or an idle delay. One configuration applies to both platforms:
LiquidGlassNavBar(
currentIndex: currentIndex,
onTap: onTap,
scrollConfiguration: const LiquidGlassNavBarScrollConfiguration(
collapsedScale: 0.82,
collapseThreshold: 12,
expandThreshold: 12,
animationDuration: Duration(milliseconds: 280),
idleExpandDuration: Duration(seconds: 5),
),
items: items,
)
Use iosScrollConfiguration or androidScrollConfiguration only when one
platform needs different values. The former
LiquidGlassIOSNavBarScrollConfiguration and
LiquidGlassAndroidNavBarScrollConfiguration names remain as deprecated type
aliases.
The behavior also expands at the top and when an item is selected. It listens
through the nearest ScrollNotificationObserver; a Scaffold supplies one
automatically. Wrap custom layouts in ScrollNotificationObserver when no
Scaffold ancestor is present. Each configuration is ignored outside its
target platform, and both are ignored on web and desktop.
icon and activeIcon accept arbitrary widgets and inherit the active
IconTheme and DefaultTextStyle. Do not set an explicit colour when the
widget should follow selected/unselected styling. Native iOS uses SF Symbol
names from iosSystemImage and iosSelectedSystemImage; when omitted, it uses
the neutral circle symbol.
Native iOS fidelity
Native surfaces preserve all four Flutter corner radii. On iOS 26 each surface
uses SwiftUI glassEffect directly; Reduce Transparency replaces blur with a
strong opaque tint, Increase Contrast strengthens tint and borders, and Reduce
Motion disables interactive glass and nav spring motion. iOS 16-25 uses the
same per-corner shape and accessibility adaptations with system material.
Each PlatformGlass is a separate UiKitView, so sibling surfaces cannot
share one GlassEffectContainer or native morphing namespace. Wrapping each
isolated view in its own container adds rendering work without enabling
grouping, so the package deliberately avoids that. True cross-widget morphing
requires a future single native host that receives all Flutter surface
geometries.
Shared settings
Define a baseline once with LiquidGlassSettingsScope:
LiquidGlassSettingsScope(
settings: LiquidGlassSettings.matteDark.copyWith(
tintColor: const Color(0xFF9333EA),
tintOpacity: 0.30,
),
child: const MyAppContent(),
)
Or define settings while grouping non-overlapping fallback surfaces:
LiquidGlassBackdropGroup(
settings: const LiquidGlassSettings.matteDark,
child: ListView(children: glassCards),
)
Page-specific settings use the same boundary. Every descendant component inherits the page color unless it supplies local settings:
LiquidGlassBackdropGroup(
settings: const LiquidGlassSettings(
androidColor: Color(0xFF355C68),
),
child: const SavedPage(),
)
Settings resolve in this order:
- Settings passed directly to a card, button, nav bar, or
PlatformGlass. - The nearest
LiquidGlassSettingsScopeor configured backdrop group. LiquidGlassSettings.matteLight.
A component can override the shared baseline:
const LiquidGlassCard(
settings: LiquidGlassSettings(
tintColor: Color(0xFF16A34A),
tintOpacity: 0.32,
),
child: Text('Local green tint'),
)
Settings reference
| Parameter | Default | Behavior |
|---|---|---|
tintColor |
null |
Custom surface color. Null selects an adaptive matte tint on the fallback. |
androidColor |
null |
Exact solid Android color. When set, skips blur, gradient, border, and shadow. Ignored on other platforms. |
tintOpacity |
0.15 |
Tint strength from 0.0 to 1.0. Saturated colors usually work well at 0.20-0.40. |
blurSigma |
20 |
Requested backdrop blur. Forwarded to native iOS and capped on Android. |
androidBlurSigma |
8 |
Maximum Android blur. Set to 0 for a fast matte-only surface. |
borderOpacity |
0.25 |
Fallback border-highlight opacity. |
borderWidth |
1 |
Fallback border width in logical pixels. |
shadowOpacity |
0.12 |
Fallback drop-shadow opacity. Set to 0 on dense screens to reduce paint cost. |
shadowBlurRadius |
16 |
Fallback shadow blur radius. |
shadowOffset |
(0, 4) |
Fallback shadow offset. |
Built-in presets:
LiquidGlassSettings.matteLight: bright neutral glass.LiquidGlassSettings.matteDark: strong charcoal glass with a lower Android blur cap.
For a visible custom tint, provide both color and opacity:
const LiquidGlassSettings(
tintColor: Color(0xFF9333EA),
tintOpacity: 0.30,
)
Android performance
For maximum performance, provide androidColor. Android then paints one solid
color and skips all glass effects. Cards and buttons also expose this as a
direct per-component shortcut:
LiquidGlassBackdropGroup(
settings: const LiquidGlassSettings(
androidColor: Color(0xFF263238),
),
child: ListView(
children: const [
LiquidGlassCard(child: Text('Inherited page color')),
LiquidGlassCard(
androidColor: Color(0xFF00695C),
child: Text('Card override'),
),
LiquidGlassButton(
androidColor: Color(0xFF3949AB),
onPressed: null,
child: Text('Button override'),
),
],
),
)
On iOS these components continue using native glass. To opt one Android
component back into glass inside a solid page scope, pass local settings with
androidColor omitted.
Wrap scrollable sections containing several glass surfaces in
LiquidGlassBackdropGroup. It coordinates inherited settings and, by default,
pauses blur and blurred shadows while scrolling while preserving tint, border,
and content. Backdrop filters remain independent for consistent Android color:
LiquidGlassBackdropGroup(
shareBackdropFilters: false,
disableBlurWhileScrolling: true,
disableShadowsWhileScrolling: true,
effectRestoreDelay: Duration(milliseconds: 80),
child: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) => LiquidGlassCard(
child: ItemRow(items[index]),
),
),
)
The short restore delay prevents expensive effects from being recreated
between closely spaced scroll notifications. Set either disable flag to
false to keep that effect active during motion, or set effectRestoreDelay
to Duration.zero for immediate restoration. Other useful optimizations:
-
Set
shareBackdropFilters: trueonly for non-overlapping surfaces after checking target Android devices. It reduces blur passes, but grouped filters can render the first visible surface with different brightness on some Android renderers while scroll clipping changes. -
Lower
androidBlurSigmato6-8, or0for matte-only rendering. -
Reduce
shadowBlurRadiusor setshadowOpacityto0in long lists. -
Use lazy lists such as
ListView.builder. -
Avoid continuously animated backgrounds behind large blurred surfaces.
-
Use
LiquidGlassScrollBehaviorto remove Android overscroll effects that can alter backdrop sampling.
Do not put overlapping PageView pages, route transitions, or floating surfaces
in one backdrop group. Grouped filters share input and overlapping surfaces can
sample the wrong backdrop. LiquidGlassNavBar already disables shared backdrop
filtering for its floating fallback surface.
Rendering mode
if (isNativeLiquidGlassSupported) {
print('Native iOS surface');
} else {
print('Flutter fallback surface');
}
This value is true on iOS even before iOS 26 because those systems still use a native material fallback. Android, web, and desktop return false.
Requirements
- Flutter 3.19 or newer
- Dart 3.0 or newer
- iOS 16+; iOS 26+ uses native Liquid Glass
- Android API 21+
Libraries
- flutter_liquid_glass_kit
- Platform-adaptive Liquid Glass components for Flutter.
- liquid_glass_kit
- Backwards-compatible export.