native_liquid_glass 0.2.6
native_liquid_glass: ^0.2.6 copied to clipboard
Liquid Glass iOS platform views for Flutter using native UIKit components.
native_liquid_glass #
A Flutter plugin that exposes iOS native Liquid Glass widgets through Flutter Platform Views (UiKitView).
On iOS 26+, all widgets automatically adopt Apple's Liquid Glass visual style. On older iOS versions and non-iOS platforms, graceful fallbacks are provided.
Widgets #
| Widget | Native UIKit component |
|---|---|
LiquidGlassTabBar |
UITabBarController |
LiquidGlassButton |
UIButton with glass configurations |
LiquidGlassButtonGroup |
Row/column of UIButtons with unified glass blending |
LiquidGlassContainer |
Glass-effect UIView with custom shapes, animated transitions, and interactive press |
LiquidGlassNavigationBar |
UINavigationBar |
LiquidGlassToolbar |
UIToolbar |
LiquidGlassSearchBar |
Expandable UISearchTextField |
LiquidGlassSearchScaffold |
Full-screen scaffold with native tab bar + UISearchTab |
LiquidGlassToggle |
UISwitch |
LiquidGlassSlider |
UISlider |
LiquidGlassStepper |
UIStepper |
LiquidGlassSegmentedControl |
UISegmentedControl |
LiquidGlassColorPicker |
UIColorWell |
LiquidGlassDatePicker |
UIDatePicker |
LiquidGlassMenu |
UIButton + UIMenu context menu |
LiquidGlassSheet |
UISheetPresentationController |
LiquidGlassAlert |
UIAlertController |
LiquidGlassPopover |
UIPopoverPresentationController |
LiquidGlassActivityIndicator |
UIActivityIndicatorView |
LiquidGlassProgressView |
UIProgressView |
Requirements #
- Flutter 3.41.2+
- iOS/iPadOS 26.0+ for full Liquid Glass behavior (older iOS versions fall back to standard system styles)
Integration #
This plugin supports both CocoaPods and Swift Package Manager (SPM).
- CocoaPods (default) — works automatically with
flutter pub get - SPM — supported from Flutter 3.19+ via
ios/native_liquid_glass/Package.swift
Installation #
dependencies:
native_liquid_glass: ^0.2.6
Then run flutter pub get.
Icons #
All icon-bearing widgets accept NativeLiquidGlassIcon, which supports three sources:
NativeLiquidGlassIcon.sfSymbol('star.fill') // Native SF Symbol (preferred on iOS)
NativeLiquidGlassIcon.iconData(Icons.star) // Flutter IconData (PNG-encoded for iOS)
NativeLiquidGlassIcon.asset('assets/star.png') // App bundle asset (PNG or SVG)
On iOS, source resolution priority is asset → IconData → SF Symbol.
Usage #
LiquidGlassTabBar #
LiquidGlassTabBar(
items: const [
LiquidGlassTabItem(
label: 'Home',
icon: NativeLiquidGlassIcon.sfSymbol('house'),
selectedIcon: NativeLiquidGlassIcon.sfSymbol('house.fill'),
selectedItemColor: Color(0xFF007AFF),
iosBadgeValue: '3',
),
LiquidGlassTabItem(
label: 'Search',
icon: NativeLiquidGlassIcon.sfSymbol('magnifyingglass'),
),
LiquidGlassTabItem(
label: 'Profile',
icon: NativeLiquidGlassIcon.iconData(Icons.person_outline),
selectedIcon: NativeLiquidGlassIcon.iconData(Icons.person),
),
],
currentIndex: _selectedIndex,
onTabSelected: (index) => setState(() => _selectedIndex = index),
height: 72,
selectedItemColor: Colors.blue,
labelTextStyle: const TextStyle(fontSize: 11, fontWeight: FontWeight.w600),
)
LiquidGlassButton #
// Text button
LiquidGlassButton(
label: 'Continue',
onPressed: () {},
icon: NativeLiquidGlassIcon.sfSymbol('arrow.right'),
style: LiquidGlassButtonStyle.prominentGlass,
)
// Icon-only button
LiquidGlassButton.icon(
onPressed: () {},
icon: NativeLiquidGlassIcon.sfSymbol('heart.fill'),
tint: const Color(0xFFFF375F),
size: 50,
)
LiquidGlassButtonGroup #
LiquidGlassButtonGroup(
buttons: [
LiquidGlassButtonData(
label: 'Bold',
icon: NativeLiquidGlassIcon.sfSymbol('bold'),
onPressed: () {},
),
LiquidGlassButtonData(
label: 'Italic',
icon: NativeLiquidGlassIcon.sfSymbol('italic'),
onPressed: () {},
),
],
axis: Axis.horizontal,
spacing: 8,
)
LiquidGlassContainer #
// Basic container
LiquidGlassContainer(
config: const LiquidGlassConfig(
effect: LiquidGlassEffect.regular,
shape: LiquidGlassEffectShape.capsule,
tint: Color(0xFF007AFF),
interactive: true,
),
width: 200,
height: 80,
onTap: () => print('tapped'),
child: const Center(child: Text('Glass!')),
)
// Wraps child when width/height omitted (like Flutter Container)
LiquidGlassContainer(
config: const LiquidGlassConfig(shape: LiquidGlassEffectShape.rect),
child: Padding(
padding: EdgeInsets.all(20),
child: Text('Wraps content'),
),
)
// Custom SVG shape (hand-built)
LiquidGlassContainer(
config: LiquidGlassConfig(
shape: LiquidGlassEffectShape.custom,
customPath: [
LiquidGlassPathOp.moveTo(50, 0),
LiquidGlassPathOp.lineTo(150, 0),
LiquidGlassPathOp.lineTo(200, 60),
LiquidGlassPathOp.lineTo(150, 120),
LiquidGlassPathOp.lineTo(50, 120),
LiquidGlassPathOp.lineTo(0, 60),
LiquidGlassPathOp.close(),
],
customPathSize: Size(200, 120),
),
width: 200,
height: 120,
child: const Center(child: Text('Diamond')),
)
// Custom shape from an SVG path string (`d` attribute of <path>)
LiquidGlassContainer(
config: LiquidGlassConfig(
shape: LiquidGlassEffectShape.custom,
customPath: 'M50 0 L150 0 L200 60 L150 120 L50 120 L0 60 Z'
.toLiquidGlassPath(),
customPathSize: const Size(200, 120),
),
width: 200,
height: 120,
child: const Center(child: Text('Diamond')),
)
// Animated transitions between shapes/effects
LiquidGlassContainer(
config: LiquidGlassConfig(shape: _currentShape),
animateChanges: true, // spring transition on config changes
child: myContent,
)
LiquidGlassNavigationBar #
LiquidGlassNavigationBar(
title: 'Settings',
leadingItems: const [
LiquidGlassNavBarItem(
id: 'back',
icon: NativeLiquidGlassIcon.sfSymbol('chevron.left'),
label: 'Back',
),
],
trailingItems: const [LiquidGlassNavBarItem(id: 'done', label: 'Done')],
onItemTapped: (id) {},
)
LiquidGlassToolbar #
LiquidGlassToolbar(
items: const [
LiquidGlassToolbarItem(id: 'share', icon: NativeLiquidGlassIcon.sfSymbol('square.and.arrow.up')),
LiquidGlassToolbarSpacer(),
LiquidGlassToolbarItem(id: 'done', label: 'Done'),
],
onItemTapped: (id) {},
)
Controls #
// Toggle
LiquidGlassToggle(value: _isOn, onChanged: (v) => setState(() => _isOn = v))
// Slider
LiquidGlassSlider(value: _volume, min: 0, max: 1, onChanged: (v) => setState(() => _volume = v))
// Stepper
LiquidGlassStepper(value: _count, min: 0, max: 10, onChanged: (v) => setState(() => _count = v))
// Segmented control
LiquidGlassSegmentedControl(
labels: const ['Day', 'Week', 'Month'],
selectedIndex: _period,
onValueChanged: (i) => setState(() => _period = i),
)
Indicators #
LiquidGlassActivityIndicator(animating: _isLoading)
LiquidGlassProgressView(progress: _uploadProgress, progressTintColor: Colors.blue)
Search #
LiquidGlassSearchBar(
placeholder: 'Search',
expandable: true,
onChanged: (query) {},
onSubmitted: (query) {},
)
Pickers #
LiquidGlassColorPicker(
selectedColor: _selectedColor,
onColorChanged: (color) => setState(() => _selectedColor = color),
)
LiquidGlassDatePicker(
mode: LiquidGlassDatePickerMode.date,
initialDate: DateTime.now(),
onDateChanged: (date) {},
)
Context Menu #
LiquidGlassMenu(
items: const [
LiquidGlassMenuItem(
id: 'copy',
title: 'Copy',
icon: NativeLiquidGlassIcon.sfSymbol('doc.on.doc'),
),
LiquidGlassMenuItem(id: 'delete', title: 'Delete', isDestructive: true),
],
onItemSelected: (id) {},
label: 'Actions',
)
Modals #
// Alert
final actionId = await LiquidGlassAlert.show(
context: context,
title: 'Delete?',
message: 'This cannot be undone.',
actions: [
const LiquidGlassAlertAction(id: 'delete', title: 'Delete', isDestructive: true),
const LiquidGlassAlertAction(id: 'cancel', title: 'Cancel', isCancel: true),
],
);
// Sheet
LiquidGlassSheet.show(
context: context,
title: 'Options',
detents: [LiquidGlassSheetDetent.medium],
);
// Popover
LiquidGlassPopover.show(
context: context,
builder: (_) => const Text('Popover content'),
anchorRect: Offset.zero & const Size(50, 50),
);
Overlay suppression #
Native glass platform views sit above Flutter's rendering surface. When Flutter shows overlays (showModalBottomSheet, showDialog, page transitions), the glass can bleed through.
This is handled automatically — every glass widget hides itself when its route is no longer current and restores when it is. Glass items inside the overlay stay visible. No setup required.
For custom overlays that don't go through the Navigator, use the manual API:
NativeLiquidGlassLifecycle.suppressGlassEffects();
await showMyCustomOverlay();
NativeLiquidGlassLifecycle.unsuppressGlassEffects();
Device qualification check #
if (NativeLiquidGlassUtils.supportsLiquidGlass) {
// Running on iOS 26+ — full Liquid Glass behavior active.
}
Spring Animation System #
Cupertino-style spring physics for Flutter animations, built on SpringSimulation.
Presets #
| Preset | Duration | Bounce | Use case |
|---|---|---|---|
LiquidGlassSpring.bouncy() |
500ms | 0.3 | Playful, expressive |
LiquidGlassSpring.snappy() |
500ms | 0.15 | Quick UI transitions |
LiquidGlassSpring.smooth() |
500ms | 0.0 | Critically-damped, no overshoot |
LiquidGlassSpring.interactive() |
150ms | 0.14 | Tracking a pointer |
SpringBuilder #
SpringBuilder(
value: _expanded ? 1.5 : 1.0,
spring: LiquidGlassSpring.bouncy(),
builder: (context, value, child) {
return Transform.scale(scale: value, child: child);
},
child: myWidget,
)
VelocitySpringBuilder (drag + release) #
VelocitySpringBuilder(
value: _dragOffset,
active: _isDragging,
springWhenActive: LiquidGlassSpring.interactive(),
springWhenReleased: LiquidGlassSpring.snappy(),
builder: (context, value, velocity, child) {
return Transform.translate(offset: Offset(value, 0), child: child);
},
child: myWidget,
)
Controllers (imperative API) #
final ctrl = SingleSpringController(
vsync: this,
spring: LiquidGlassSpring.snappy(),
initialValue: 0.0,
);
ctrl.animateTo(1.0); // spring to target, preserving velocity
ctrl.setValue(0.5); // instant jump
SVG Path Utility #
SvgPathExtension on String parses SVG path data (the d attribute of an <path> element) into Flutter paths or LiquidGlassConfig.customPath ops — so you can take an SVG straight from a design tool and drop it into a glass container.
// Flutter Path
final path = 'M10 10 L20 20 Z'.toPath();
// Scaled Flutter Path (from SVG viewBox to target size)
final scaled = 'M10 10 L20 20 Z'.toPathScaled(
viewBox: const Size(30, 30),
target: const Size(120, 120),
);
// Direct to LiquidGlassConfig.customPath
LiquidGlassContainer(
config: LiquidGlassConfig(
shape: LiquidGlassEffectShape.custom,
customPath: 'M50 0 L150 0 L200 60 L150 120 L50 120 L0 60 Z'
.toLiquidGlassPath(),
customPathSize: const Size(200, 120),
),
child: ...,
)
// Handles non-zero-origin viewBoxes (e.g. "1 0 24 226")
LiquidGlassContainer(
config: LiquidGlassConfig(
shape: LiquidGlassEffectShape.custom,
customPath: svgD.toLiquidGlassPathScaled(
viewBox: const Rect.fromLTWH(1, 0, 24, 226),
target: const Size(48, 452),
),
customPathSize: const Size(48, 452),
),
child: ...,
)
Leading whitespace, newlines, comments, or stray characters before the first M/m are stripped automatically, so pasted SVG strings "just work". Quadratic Béziers are normalized to cubic Béziers to match iOS's path rendering.
How it works #
Each widget creates a UiKitView that Flutter embeds into the render tree. The iOS plugin registers a native FlutterPlatformViewFactory for each view type. Initial configuration is passed through creationParams; subsequent changes are pushed over a per-view FlutterMethodChannel.
On iOS 26+, all native UIKit controls automatically receive Apple's Liquid Glass styling. On older iOS versions, the same controls render with their standard system appearance.
Notes #
- Tab bar background, shadow, and badge styling are intentionally system-driven.
- Widgets that require custom icon data (non-SF Symbol) rasterize or load assets asynchronously before the platform view is shown; a same-size placeholder is displayed during resolution.
- See
example/for a working demo of all widgets.
Happy building.
Thanks for trying native_liquid_glass.