liquid_glass_ios_widgets 0.3.1
liquid_glass_ios_widgets: ^0.3.1 copied to clipboard
Native iOS 26 Liquid Glass components (buttons, switch, slider, segmented control, glass container, tab bar) as Flutter widgets via platform views.
liquid_glass_ios_widgets #
Native iOS 26 Liquid Glass components as Flutter widgets.
Each widget is backed by a real SwiftUI / UIKit view through a platform view
(UiKitView). You get the system look and behavior: refraction, the
glass thumb on sliders and switches, the press shimmer, and the floating tab bar.
| Widget | Native component |
|---|---|
LiquidGlassButton / LiquidGlassButton.icon |
SwiftUI Button + .buttonStyle(.glass) / .glassProminent |
LiquidGlassSwitch |
UISwitch |
LiquidGlassSlider |
SwiftUI Slider |
LiquidGlassSegmentedControl |
SwiftUI Picker + .pickerStyle(.segmented) |
LiquidGlassContainer |
SwiftUI .glassEffect(_:in:), with a Flutter child on top |
LiquidGlassTabBar |
UITabBar |
Requirements #
- iOS only. The plugin registers only the iOS platform.
- Building requires Xcode 26+ (iOS 26 SDK).
- The iOS deployment target must be 15.0 or higher.
- Real Liquid Glass renders on iOS 26+. On iOS 15–18 the same widgets use
native materials and
.borderedbutton styles. - On non-iOS platforms (e.g. widget tests) the widgets render Cupertino fallbacks.
Set the minimum iOS version in ios/Podfile:
platform :ios, '15.0'
Usage #
import 'package:liquid_glass_ios_widgets/liquid_glass_ios_widgets.dart';
LiquidGlassButton(
label: 'Share',
systemImage: 'square.and.arrow.up', // SF Symbols name
onPressed: () {},
);
LiquidGlassButton.icon(
systemImage: 'plus',
style: LiquidGlassButtonStyle.prominent,
tint: Colors.orange,
onPressed: () {},
);
LiquidGlassSwitch(
value: enabled,
onChanged: (value) => setState(() => enabled = value),
);
LiquidGlassSlider(
value: volume,
onChanged: (value) => setState(() => volume = value),
);
LiquidGlassSegmentedControl(
selectedIndex: index,
onChanged: (i) => setState(() => index = i),
segments: const [
LiquidGlassSegment(label: 'Day'),
LiquidGlassSegment(label: 'Week'),
LiquidGlassSegment(systemImage: 'calendar'),
],
);
LiquidGlassContainer(
shape: const LiquidGlassShape.rect(cornerRadius: 24),
padding: const EdgeInsets.all(16),
child: const Text('Flutter content on native glass'),
);
The tab bar is meant to float over content, so the glass has something to refract:
Stack(
children: [
content,
Positioned(
left: 0,
right: 0,
bottom: MediaQuery.paddingOf(context).bottom,
child: LiquidGlassTabBar(
currentIndex: tab,
onTap: (i) => setState(() => tab = i),
items: const [
LiquidGlassTabItem(label: 'Home', systemImage: 'house', selectedSystemImage: 'house.fill'),
LiquidGlassTabItem(label: 'Search', systemImage: 'magnifyingglass'),
],
),
),
],
);
Checking for Liquid Glass support #
LiquidGlass.isSupportLiquidGlass is a synchronous bool. It's true on
iOS 26+ and false on older iOS versions and other platforms, so you can use
it directly in build:
if (LiquidGlass.isSupportLiquidGlass) {
// real Liquid Glass
}
LiquidGlassAdaptive shows its first widget when Liquid Glass is supported
and its second widget otherwise:
LiquidGlassAdaptive(
glass: LiquidGlassButton(label: 'Save', onPressed: save),
fallback: CupertinoButton.filled(onPressed: save, child: const Text('Save')),
);
await LiquidGlass.isSupported() asks the native side for the same
answer.
How it works #
Flutter widget ──creationParams──▶ UiKitView ──▶ FlutterPlatformViewFactory
▲ │
│ MethodChannel "liquid_glass_ios_widgets/view_<id>"
│ Dart → iOS: update(params) ▼
└──── iOS → Dart: onPressed / onChanged / … UIHostingController(SwiftUI view)
intrinsicSize or a UIKit control
- Each native view gets its own method channel. When the widget rebuilds with
new parameters, the whole parameter map is sent with
update. - Widgets without an explicit size, such as buttons and the segmented
control, are sized from the size the native side measures and reports
(
intrinsicSize). - Controls are controlled: rebuild with the new value in
onChanged. If you don't, the native control snaps back to the value you passed.
Dark mode #
Native views follow the Flutter theme's brightness
(CupertinoTheme.brightnessOf(context), which inside a MaterialApp comes
from the Material theme), not the iOS system appearance. So an in-app
light/dark switch (ThemeMode) applies to the native components too.
Limitations #
-
Light theme on a dark system. As of iOS 26, the Liquid Glass material follows the device's system appearance and can't be lightened per view, so with the system in dark mode and the app in a light theme, glass surfaces stay dark. The text and controls inside them follow the app theme. A dark app theme on a light system works fully.
-
Performance. Every widget is a separate platform view. Dozens of them in a scrolling list cost more than plain Flutter widgets.
-
No morphing between widgets.
GlassEffectContainermerging and morphing only works inside a single SwiftUI hierarchy, so it can't happen between separate Flutter widgets. -
The glass refracts content Flutter draws behind the platform view. Content drawn on top of it (e.g.
LiquidGlassContainer.child) is not refracted. -
Icons are SF Symbols names (
'heart.fill'), not FlutterIconData.
Example #
cd example && flutter run