native_liquid_glass_flutter 0.0.3
native_liquid_glass_flutter: ^0.0.3 copied to clipboard
Native iOS Liquid Glass surfaces, controls, overlays, menus, and adaptive Flutter fallbacks for Flutter apps.
native_liquid_glass_flutter #
Native iOS Liquid Glass surfaces, system overlays, and adaptive Flutter fallbacks for apps that need an iOS-native feel without forcing that design onto Android.
The package is a Flutter plugin because the iOS implementation uses Swift code:
- iOS 26 and newer: SwiftUI Liquid Glass through
glassEffect. - iOS 13 through iOS 25: UIKit
UIVisualEffectViewmaterial fallback. - Android, desktop, web, and tests: Flutter-rendered superellipse glass fallback.
By default, regular content and controls stay in Flutter. Native iOS surfaces are
used automatically for chrome, floating surfaces, and modals, or anywhere you
explicitly opt in with LiquidGlassNativePolicy.native.
The example app is a component gallery. It covers surfaces, navigation, live sliders, switches, segmented controls, steppers, menus, pull-down buttons, sheets, alerts, action sheets, option pickers, date/time pickers, share sheets, and configuration changes.
Installation #
dependencies:
native_liquid_glass_flutter: ^0.0.3
Quick Start #
import 'package:flutter/material.dart';
import 'package:native_liquid_glass_flutter/native_liquid_glass_flutter.dart';
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return LiquidGlassTheme(
data: LiquidGlassThemeData.fromColorScheme(colorScheme),
child: LiquidGlassScaffold(
appBar: const LiquidGlassAppBar(
center: Icon(Icons.auto_awesome_rounded),
),
bottomNavigationBar: LiquidGlassTabBar(
selectedIndex: 0,
onSelected: (index) {},
items: const <LiquidGlassTabItem>[
LiquidGlassTabItem(
icon: Icon(Icons.home_outlined),
selectedIcon: Icon(Icons.home_rounded),
label: Text('Home'),
nativeSymbol: 'house',
nativeSelectedSymbol: 'house.fill',
),
LiquidGlassTabItem(
icon: Icon(Icons.book_outlined),
selectedIcon: Icon(Icons.book_rounded),
label: Text('Read'),
nativeSymbol: 'book',
nativeSelectedSymbol: 'book.fill',
),
],
),
body: const ListView(
padding: EdgeInsets.fromLTRB(20, 24, 20, 112),
children: <Widget>[
LiquidGlassSurface(
padding: EdgeInsets.all(18),
child: Text('Flutter content over adaptive glass.'),
),
],
),
),
);
}
}
Component Matrix #
| Component | iOS implementation | Other platforms |
|---|---|---|
LiquidGlassSurface |
Automatic policy keeps content in Flutter and uses SwiftUI/UIKit material for chrome, floating surfaces, and modals | Flutter superellipse + blur fallback |
LiquidGlassTabBar |
UITabBar platform view for chrome, bridged to Flutter selection state |
Flutter glass tab bar |
LiquidGlassSlider |
Flutter Slider by default, optional UISlider with nativePolicy: LiquidGlassNativePolicy.native |
Flutter Slider |
LiquidGlassSwitch |
Flutter Switch by default, optional UISwitch with nativePolicy: LiquidGlassNativePolicy.native |
Flutter Switch |
LiquidGlassSegmentedControl |
Flutter SegmentedButton by default, optional UISegmentedControl with native policy |
Flutter SegmentedButton |
LiquidGlassStepper |
Flutter icon buttons by default, optional UIStepper with native policy |
Flutter icon buttons |
LiquidGlassMenuButton |
UIButton with native UIMenu and UIAction items |
Flutter option picker |
LiquidGlassPullDownButton |
UIButton with native UIMenu command actions |
Flutter option picker |
showLiquidGlassActionSheet |
UIAlertController.actionSheet |
Package glass bottom sheet |
showLiquidGlassAlert |
UIAlertController.alert |
Package glass dialog |
showLiquidGlassOptionPicker |
UIAlertController.actionSheet |
Package glass action sheet |
showLiquidGlassDatePicker |
UIDatePicker in native action sheet |
Flutter showDatePicker |
showLiquidGlassTimePicker |
UIDatePicker in native action sheet |
Flutter showTimePicker |
showLiquidGlassShareSheet |
UIActivityViewController |
Snackbar fallback |
Component Gallery #
Each screenshot is paired with the component or API that produced it.
Environment And Layout #
Directionality, Localizations.localeOf, and Theme are sent through the
native bridge. Mounted UIKit views resync when the app changes theme, text
direction, or locale.
Environment controls
[Environment controls]
RTL and Arabic locale
[RTL and Arabic locale]
LiquidGlassSurface
[LiquidGlassSurface]
Navigation #
Use native navigation chrome when the OS should own safe-area sizing, title placement, item spacing, labels, SF Symbols, badges, and disabled states while Flutter keeps routing and selected state.
LiquidGlassAppBar
[LiquidGlassAppBar]
LiquidGlassTabBar
[LiquidGlassTabBar]
Controls #
Core controls are Flutter-first by default. Set
nativePolicy: LiquidGlassNativePolicy.native when an iOS screen should use the
matching UIKit control.
LiquidGlassButton
[LiquidGlassButton]
LiquidGlassSlider
[LiquidGlassSlider]
LiquidGlassSlider with endpoint symbols
[LiquidGlassSlider endpoint symbols]
LiquidGlassSwitch
[LiquidGlassSwitch]
LiquidGlassSegmentedControl
[LiquidGlassSegmentedControl]
LiquidGlassStepper
[LiquidGlassStepper]
Menus And Pull-Down Buttons #
Menus are backed by UIKit UIButton, UIMenu, and UIAction. Selection menus
show the current value and let UIKit draw the checkmark. Command menus keep the
button label stable and return the selected command to Flutter.
LiquidGlassMenuButton
[LiquidGlassMenuButton]
Open UIMenu
[Open UIMenu]
LiquidGlassPullDownButton
[LiquidGlassPullDownButton]
Icon action menu
[Icon action menu]
Grouped command menu
[Grouped command menu]
Pull-down command opening a native slider sheet
[Pull-down slider command]
Sheets, Overlays, And Pickers #
Overlays use native UIKit presenters on iOS and Flutter fallbacks elsewhere.
showLiquidGlassSheet
[showLiquidGlassSheet]
showLiquidGlassAlert
[showLiquidGlassAlert]
showLiquidGlassActionSheet
[showLiquidGlassActionSheet]
showLiquidGlassOptionPicker
[showLiquidGlassOptionPicker]
showLiquidGlassDatePicker
[showLiquidGlassDatePicker]
showLiquidGlassTimePicker
[showLiquidGlassTimePicker]
showLiquidGlassShareSheet
[showLiquidGlassShareSheet]
Widgets #
LiquidGlassSurface #
Use LiquidGlassSurface for app bars, bottom bars, sheets, floating controls,
and small grouped controls.
LiquidGlassSurface(
configuration: const LiquidGlassConfiguration(
cornerRadius: 32,
cornerStyle: LiquidGlassCornerStyle.all,
tintOpacity: 0.18,
interactive: true,
),
padding: const EdgeInsets.all(16),
child: const Text('Glass content'),
)
LiquidGlassAppBar #
LiquidGlassAppBar keeps leading/back behavior directional by using Flutter's
native BackButton. Simple text-title app bars can stay native on iOS while
exposing trailing UIBarButtonItem actions back to Flutter.
LiquidGlassAppBar(
title: const Text('Reader'),
nativeActions: const <LiquidGlassAppBarAction>[
LiquidGlassAppBarAction(
title: 'Bookmark',
value: 'bookmark',
nativeSymbol: 'bookmark',
),
],
onNativeActionSelected: handleAppBarAction,
)
LiquidGlassTabBar #
The tab bar is designed to be overlaid by LiquidGlassScaffold, so it does not
reserve layout height like a normal Scaffold.bottomNavigationBar.
LiquidGlassTabBar(
selectedIndex: selectedIndex,
onSelected: onTabChanged,
iconTextGap: 6,
items: const <LiquidGlassTabItem>[
LiquidGlassTabItem(
icon: Icon(Icons.home_outlined),
label: Text('Home'),
nativeSymbol: 'house',
nativeSelectedSymbol: 'house.fill',
),
LiquidGlassTabItem(
icon: Icon(Icons.alarm_outlined),
label: Text('Alerts'),
nativeSymbol: 'alarm',
nativeSelectedSymbol: 'alarm.fill',
badge: '2',
),
LiquidGlassTabItem(
icon: Icon(Icons.settings_outlined),
label: Text('Settings'),
nativeSymbol: 'gearshape',
enabled: false,
),
],
)
On iOS the tab bar is a native UITabBar platform view pinned by
LiquidGlassScaffold. Flutter still owns the selected index and routing through
onSelected, while the system owns the item layout, safe-area height, and SF
Symbol rendering.
Controls And Native Opt-In #
LiquidGlassSlider, LiquidGlassSwitch, LiquidGlassSegmentedControl, and
LiquidGlassStepper stay Flutter-first in automatic mode. Set
nativePolicy: LiquidGlassNativePolicy.native when a focused iOS screen should
use the UIKit control. Native controls keep their values in sync through
per-view method channels and eager gesture forwarding. The slider calls
onChanged while the thumb moves, so previews can update immediately inside
sheets. Native glass surfaces are decorative pass-through layers when Flutter
owns the controls above them.
double textScale = 1;
LiquidGlassSlider(
value: textScale,
min: 0.8,
max: 1.4,
step: 0.01,
nativePolicy: LiquidGlassNativePolicy.native,
minimumNativeSymbol: 'textformat.size.smaller',
maximumNativeSymbol: 'textformat.size.larger',
isContinuous: false,
onChanged: (value) {
setState(() => textScale = value);
},
onChangeEnd: saveTextScale,
)
LiquidGlassSegmentedControl(
selectedIndex: selectedIndex,
onChanged: (index) => setState(() => selectedIndex = index),
segments: const <LiquidGlassSegment>[
LiquidGlassSegment(label: 'Subtle'),
LiquidGlassSegment(label: 'Regular'),
LiquidGlassSegment(label: 'Bold'),
],
)
LiquidGlassSwitch(
value: enabled,
nativePolicy: LiquidGlassNativePolicy.native,
onChanged: (value) => setState(() => enabled = value),
)
LiquidGlassStepper(
value: count.toDouble(),
min: 0,
max: 10,
nativePolicy: LiquidGlassNativePolicy.native,
onChanged: (value) => setState(() => count = value.round()),
)
LiquidGlassMenuButton is a native iOS UIMenu control by default. Use it for
compact option sets where the selected value should stay visible.
LiquidGlassMenuButton(
title: 'Density',
value: density,
onChanged: (value) => setState(() => density = value),
options: const <LiquidGlassAction>[
LiquidGlassAction(
title: 'Compact',
value: 'compact',
nativeSymbol: 'rectangle.compress.vertical',
group: 'Density',
),
LiquidGlassAction(
title: 'Comfortable',
value: 'comfortable',
nativeSymbol: 'rectangle.split.3x1',
role: LiquidGlassActionRole.preferred,
group: 'Density',
),
LiquidGlassAction(
title: 'Spacious',
value: 'spacious',
nativeSymbol: 'rectangle.expand.vertical',
group: 'Density',
),
],
)
LiquidGlassPullDownButton uses the same native UIMenu bridge for related
commands. It does not track a selected value or change its title after a command
runs.
LiquidGlassPullDownButton(
title: 'More',
width: 128,
onSelected: (value) => handleCommand(value),
actions: const <LiquidGlassAction>[
LiquidGlassAction(title: 'Duplicate', value: 'duplicate'),
LiquidGlassAction(
title: 'Archive',
value: 'archive',
nativeSymbol: 'archivebox',
),
LiquidGlassAction(
title: 'Delete',
value: 'delete',
role: LiquidGlassActionRole.destructive,
nativeSymbol: 'trash',
),
],
)
Leave width unset for full-width settings rows. Set it for compact toolbar,
form, or inline command cases.
For compact toolbar actions, keep the accessibility title and provide both a Flutter icon and the matching native SF Symbol name.
LiquidGlassPullDownButton(
title: 'Actions',
icon: const Icon(Icons.more_horiz_rounded),
nativeSymbol: 'ellipsis.circle',
showTitle: false,
onSelected: (value) => handleCommand(value),
actions: const <LiquidGlassAction>[
LiquidGlassAction(title: 'Duplicate', value: 'duplicate'),
LiquidGlassAction(title: 'Archive', value: 'archive'),
],
)
Use pull-down commands to open richer Flutter content when the command needs
custom controls instead of a simple UIAction.
LiquidGlassPullDownButton(
title: 'Adjust',
width: 140,
onSelected: (value) async {
if (value == 'adjust') {
await showLiquidGlassSheet<void>(
context: context,
title: const Text('Adjust intensity'),
builder: (_) {
return LiquidGlassSlider(
value: intensity,
nativePolicy: LiquidGlassNativePolicy.native,
minimumNativeSymbol: 'sun.min',
maximumNativeSymbol: 'sun.max',
onChanged: (value) => setState(() => intensity = value),
);
},
);
}
},
actions: const <LiquidGlassAction>[
LiquidGlassAction(title: 'Adjust intensity', value: 'adjust'),
LiquidGlassAction(title: 'Reset', value: 'reset'),
],
)
Sheets #
Custom Flutter content uses a Flutter bottom sheet with the package glass surface. The sheet automatically follows the keyboard and can use content, medium, or large detents.
await showLiquidGlassSheet<void>(
context: context,
title: const Text('Add item'),
detent: LiquidGlassSheetDetent.medium,
builder: (context) {
return const TextField(
decoration: InputDecoration(labelText: 'Name'),
);
},
);
Native iOS System Overlays #
Simple system overlays call native UIKit on iOS and fall back to Flutter elsewhere.
Native overlays complete with the selected value for user actions and null
for cancellation or dismissal. Native presentation failures surface as
PlatformException; the Flutter overlay wrappers catch those failures and show
the Flutter fallback only while the calling BuildContext is still mounted.
final action = await showLiquidGlassActionSheet(
context: context,
title: 'Choose action',
actions: const <LiquidGlassAction>[
LiquidGlassAction(
title: 'Continue',
value: 'continue',
role: LiquidGlassActionRole.preferred,
),
LiquidGlassAction(
title: 'Delete',
value: 'delete',
role: LiquidGlassActionRole.destructive,
),
],
);
final result = await showLiquidGlassAlert(
context: context,
title: 'Confirm change',
message: 'Choose one option.',
actions: const <LiquidGlassAction>[
LiquidGlassAction(
title: 'Cancel',
value: 'cancel',
role: LiquidGlassActionRole.cancel,
),
LiquidGlassAction(
title: 'Apply',
value: 'apply',
role: LiquidGlassActionRole.preferred,
),
],
);
final time = await showLiquidGlassTimePicker(
context: context,
initialTime: const TimeOfDay(hour: 8, minute: 30),
title: 'Reminder time',
);
final option = await showLiquidGlassOptionPicker(
context: context,
title: 'Glass intensity',
options: const <LiquidGlassAction>[
LiquidGlassAction(title: 'Subtle', value: 'subtle'),
LiquidGlassAction(title: 'Regular', value: 'regular'),
LiquidGlassAction(title: 'Prominent', value: 'prominent'),
],
);
final date = await showLiquidGlassDatePicker(
context: context,
initialDate: DateTime.now(),
minimumDate: DateTime(2020),
maximumDate: DateTime(2030),
);
await showLiquidGlassShareSheet(
context: context,
items: const <String>['Shared from my app'],
);
Customization #
Use LiquidGlassTheme for app-wide defaults and pass
LiquidGlassConfiguration only where a component needs a local override.
LiquidGlassTheme(
data: LiquidGlassThemeData.fromColorScheme(colorScheme).copyWith(
surface: const LiquidGlassConfiguration(
cornerRadius: 30,
tintOpacity: 0.14,
strokeOpacity: 0.22,
),
appBarHeight: 66,
tabBarHeight: 78,
),
child: const App(),
)
Important knobs:
nativePolicy: chooses automatic composition, forced Flutter, or explicit native iOS rendering.role: tells automatic policy whether a surface is content, chrome, floating, or modal.preferNative: a compatibility gate that can force surfaces back to Flutter.cornerRadius: controls continuous superellipse and native rounded shape.cornerStyle: uses all corners, top corners only, or no rounding.tintColorandtintOpacity: keep the component aligned with your app brand.interactive: marks tappable surfaces for native glass configuration; Flutter children still own gestures when a surface is used as a backdrop.intensity: chooses fallback material strength for older iOS.
Architecture #
For lifecycle rules, dependency direction, bridge contracts, and background execution posture, see doc/ARCHITECTURE.md.
The package is split by responsibility:
config: theme and serializable glass configuration.platform: native policy resolution, method channels, and iOS platform-view identifiers.surfaces: the native-backed glass surface and Flutter fallback.navigation: app bar and tab bar composition.controls: button, menu, pull-down button, and Flutter-first controls with optional UIKit-backed slider, switch, segmented control, and stepper.overlays: sheets, dialogs, action sheets, option picker, date/time picker, and share sheet helpers.scaffolds: overlay-aware scaffold behavior.ios: Swift platform views, SwiftUI Liquid Glass, UIKit controls, and UIKit overlay presenters.
The public Dart API depends on small immutable configuration objects. Widgets do not own app state, routing, localization, or dependency injection. This keeps the package usable in Provider, Riverpod, Bloc, Cubit, vanilla Flutter, or any other app architecture.
Performance #
Use native glass where it improves platform fidelity, but avoid overusing native platform views. The automatic policy is intentionally conservative: content and controls stay in Flutter unless a surface role or explicit native policy asks for native rendering.
- Prefer native glass for a small number of stable surfaces: top bars, bottom bars, sheets, and floating controls.
- Avoid putting native glass surfaces in large scrolling lists.
- Avoid putting many native platform-view controls in long scrolling lists. Prefer explicit native controls only for focused forms, settings panels, and overlays.
- Keep
LiquidGlassConfigurationstable; do not recreate highly customized platform-view surfaces every animation frame. - For live sliders, update lightweight preview state in
onChangedand persist expensive work inonChangeEnd. - Keep slider
stepreasonable. Very small steps can produce excessive state updates if the host app performs heavy work duringonChanged. - Use Flutter fallback surfaces for repeated list rows, chips, and dense content.
- Keep blur behind readable content simple. Glass should clarify hierarchy, not become the dominant visual layer.
- Use
LiquidGlassScaffoldfor bottom bars that should overlay content and hide above the keyboard. - Test dark mode, RTL, text scaling, and reduced transparency/reduced motion in the host app.
How The Native Bridge Works #
Flutter owns the public API, state, routing, and fallbacks. iOS owns only the UIKit/SwiftUI rendering or presentation that was explicitly requested.
- A Flutter widget resolves
LiquidGlassNativePolicyand decides whether to render a Flutter fallback or create an iOS platform view. - When a platform view is created, Flutter sends a typed map through
creationParams. That map includes component state, colors, symbols, enabled state, native policy, and environment keys:isDark,isRtl, andlocale. - The Swift platform view parses that map, applies semantic direction and accessibility language to the UIKit view, and configures the native control.
- Native user actions call back through the per-view
MethodChannel; Flutter updates app state and sends a new configuration back when needed. didChangeDependenciesanddidUpdateWidgetresync mounted native views, so theme, direction, and language changes rebuild native-side configuration without recreating the whole screen.- On
dispose/deinit, both sides detach handlers so stale native views do not keep sending events into removed Flutter widgets.
This keeps the package close to the pattern used by apps that link Flutter UI to native iOS workers: native surfaces and controls provide platform fidelity, but Flutter remains the source of truth for app behavior.
Rules For Adding Native Components #
Every new native component should follow the existing bridge shape:
- Start with the Apple system primitive (
UIButton,UIMenu,UISlider,UITabBar,UIAlertController, and so on) before creating custom native UI. - Keep a Flutter fallback and keep the public Dart API platform-neutral.
- Add bridge keys in
liquid_glass_bridge_keys.dartand use the shared environment configuration forisDark,isRtl, andlocale. - Sync configuration from
didChangeDependencieswhen inherited Flutter values can affect native rendering or accessibility. - Keep Swift parsing local and deterministic; use typed helper structs when the map has more than trivial fields.
- Send native events back to Flutter with method-channel callbacks instead of letting native code own routing or app state.
- Cover the component with Dart construction tests, channel resync tests, example showcase tests, and an iOS host build.
- Capture an example screenshot and document the real use case before exposing the component as package API.
Platform Notes #
Native Liquid Glass is only available when the app runs on iOS 26 or newer. Older iOS versions use native UIKit material instead. Non-iOS platforms keep the same API but render with Flutter so Android design remains under your app's control.
The package follows the current Flutter plugin model and iOS availability gates:
- Flutter plugin packages: https://docs.flutter.dev/packages-and-plugins/developing-packages
- Flutter platform views: https://docs.flutter.dev/platform-integration/ios/platform-views
- Apple Human Interface Guidelines: https://developer.apple.com/design/human-interface-guidelines/
- Apple Liquid Glass guidance: https://developer.apple.com/documentation/TechnologyOverviews/adopting-liquid-glass
- UIKit
UIMenu: https://developer.apple.com/documentation/uikit/uimenu - UIKit
UIButton.showsMenuAsPrimaryAction: https://developer.apple.com/documentation/uikit/uibutton/showsmenuasprimaryaction