mjn_liquid_ui 0.2.23
mjn_liquid_ui: ^0.2.23 copied to clipboard
Native-inspired Liquid Glass UI components for Flutter, focused on iOS SwiftUI and UIKit.
mjn_liquid_ui #
A Flutter plugin for native-inspired liquid glass UI components, focused on iOS.
mjn_liquid_ui embeds iOS SwiftUI/UIKit controls in Flutter through platform
views. It is designed for apps that want a native-feeling Liquid Glass tab bar,
search segment, switch, slider, and glass surface while keeping the Flutter app
structure.
Features #
- Liquid glass tab bar.
- Separate search tab / search segment using native SwiftUI
TabRole.search. - Liquid switch.
- Liquid slider with optional stepped values.
- Liquid glass surfaces.
- Native iOS sheet presentation with a Liquid Glass
NavigationStack+Formsheet, customizable Dart-provided form content, content-sized detents, optional background color, and background zoom. - Native iOS bottom toasts with Liquid Glass chrome, SF Symbols, and optional action callbacks.
- Optional, configurable Flutter background interaction guard for native sheets.
- SF Symbols outside the tab bar through native
UIImage(systemName:)rendering. - Native iOS-focused implementation using Swift, SwiftUI, and UIKit.
- Flutter fallbacks for unsupported platforms to avoid crashes during development.
Widgets #
| Widget | Description | Controller |
|---|---|---|
AppleLiquidTabBar |
Native iOS Liquid Glass tab bar with a dedicated search-role item, tint support, and badges | - |
AppleLiquidSwitch |
Native Liquid Glass toggle switch with animated state changes | - |
AppleLiquidSlider |
Native Liquid Glass slider with min/max range, optional step support, and optional trailing value labels | - |
AppleLiquidSymbol |
SF Symbols rendered through native UIImage(systemName:) with optional Flutter icon fallback |
- |
AppleLiquidSurface |
Apply Liquid Glass effects to any Flutter widget | - |
AppleLiquidStretch |
Flutter squash and stretch interaction wrapper for glass content | - |
AppleLiquidSheet |
Static API for presenting and dismissing native iOS Liquid Glass sheets | AppleLiquidSheetController |
AppleLiquidSheetBackgroundInteractionGuard |
Optional Flutter background interaction guard while a native sheet is active | AppleLiquidSheetController |
AppleLiquidToast |
Static API for native iOS Liquid Glass bottom toasts | - |
Icon support #
AppleLiquidSymbol renders standalone SF Symbols by name. Use
AppleLiquidSymbolWeight to select SF Symbol stroke weights such as
regular, semibold, or bold. For tab icons, pass SF Symbol names through
AppleLiquidTabItem.systemImage and optionally
AppleLiquidTabItem.activeSystemImage; use symbolWeight and
activeSymbolWeight when individual tab icons need different weights.
| API | Source |
|---|---|
AppleLiquidSymbol('name') |
Standalone SF Symbol rendered by native iOS |
AppleLiquidSymbol(weight: AppleLiquidSymbolWeight.bold) |
Optional SF Symbol stroke weight |
AppleLiquidTabItem(systemImage: 'name') |
SF Symbol for tab bar items |
AppleLiquidTabItem(activeSystemImage: 'name') |
Optional selected-state SF Symbol for tab bar items |
AppleLiquidTabItem(symbolWeight: AppleLiquidSymbolWeight.regular) |
Optional inactive tab icon weight |
AppleLiquidTabItem(activeSymbolWeight: AppleLiquidSymbolWeight.bold) |
Optional selected tab icon weight |
fallbackIcon: Icons.example |
Flutter IconData fallback for unsupported platforms |
Screenshots #
| Liquid Tab Bar | Liquid Switch |
|---|---|
![]() |
![]() |
| Liquid Slider | Liquid Surface |
|---|---|
![]() |
![]() |
| Liquid Sheet | Liquid Toast |
|---|---|
![]() |
![]() |
| Search Role Tab |
|---|
![]() |
Platform support #
| Platform | Support |
|---|---|
| iOS | Supported |
| Android | Not officially supported yet |
| Web | Not officially supported yet |
| macOS | Not officially supported yet |
| Windows | Not officially supported yet |
| Linux | Not officially supported yet |
The Dart widgets include simple Flutter fallbacks on unsupported platforms so apps should not crash during development. These fallbacks are experimental and are not official Android, web, or desktop support.
Installation #
dependencies:
mjn_liquid_ui: ^0.2.22
Then import the package:
import 'package:mjn_liquid_ui/mjn_liquid_ui.dart';
Usage #
AppleLiquidTabBar(
currentIndex: currentIndex,
selectedTintColor: const Color(0xFF007AFF),
onChanged: (int index) {
setState(() => currentIndex = index);
},
items: const <AppleLiquidTabItem>[
AppleLiquidTabItem(
title: 'Home',
systemImage: 'house.fill',
symbolWeight: AppleLiquidSymbolWeight.regular,
activeSymbolWeight: AppleLiquidSymbolWeight.bold,
),
AppleLiquidTabItem(
title: 'Jobs',
systemImage: 'briefcase.fill',
),
AppleLiquidTabItem(
title: 'Chat',
systemImage: 'message.fill',
notificationDotColor: Color(0xFF007AFF),
notificationBadgeValue: '3',
),
],
searchItem: const AppleLiquidTabItem(
title: 'Search',
systemImage: 'magnifyingglass',
isSearch: true,
),
)
Use the tab bar as an overlay when page content should continue behind the native bar:
Scaffold(
extendBody: true,
body: Stack(
children: <Widget>[
const Positioned.fill(child: YourPageContent()),
Positioned(
left: 0,
right: 0,
bottom: 0,
child: AppleLiquidTabBar(
currentIndex: currentIndex,
onChanged: (int index) {
setState(() => currentIndex = index);
},
items: const <AppleLiquidTabItem>[
AppleLiquidTabItem(title: 'Home', systemImage: 'house.fill'),
AppleLiquidTabItem(title: 'Jobs', systemImage: 'briefcase.fill'),
AppleLiquidTabItem(title: 'Chat', systemImage: 'message.fill'),
],
searchItem: const AppleLiquidTabItem(
title: 'Search',
systemImage: 'magnifyingglass',
isSearch: true,
),
),
),
],
),
)
Components #
Liquid tab bar #
AppleLiquidTabBar renders a native iOS SwiftUI TabView on iOS. The trailing
searchItem is created as a separate native search-role tab.
Use selectedTintColor to customize the selected icon and label tint while
keeping the native Liquid Glass tab bar rendering intact.
Use symbolWeight and activeSymbolWeight on AppleLiquidTabItem to tune the
normal and selected SF Symbol stroke weights for individual icons.
Set notificationDotColor on any AppleLiquidTabItem to show a notification
dot on the icon. Add notificationBadgeValue to show text inside the badge.
Liquid switch #
AppleLiquidSwitch(
value: enabled,
tintColor: const Color(0xFF14B8A6),
onChanged: (bool value) {
setState(() => enabled = value);
},
)
Liquid slider #
AppleLiquidSlider(
value: amount,
min: 0,
max: 1,
step: 0.1,
tintColor: const Color(0xFF8B5CF6),
valueLabelBuilder: (BuildContext context, double value) {
return Text('${(value * 100).round()}%');
},
onChanged: (double value) {
setState(() => amount = value);
},
)
Omit step for a continuous slider. Use valueLabel or
valueLabelBuilder to render a value to the right of the slider track; without
one, the standalone slider keeps the previous slider-only layout.
SF Symbols #
const AppleLiquidSymbol(
'sparkles',
size: 32,
color: Color(0xFF0EA5E9),
weight: AppleLiquidSymbolWeight.semibold,
fallbackIcon: Icons.auto_awesome_rounded,
semanticLabel: 'Highlights',
)
AppleLiquidSymbol renders the provided SF Symbol name natively on iOS and
paints the result as a normal Flutter image. On unsupported platforms it uses
fallbackIcon when provided. The optional weight parameter supports
ultraLight, thin, light, regular, medium, semibold, bold,
heavy, and black.
Liquid glass surface #
AppleLiquidSurface(
height: 160,
borderRadius: 28,
clear: true,
interactive: true,
deformable: true,
stretchGestureMode: AppleLiquidStretchGestureMode.gestureDetector,
child: const Text('Flutter content over native Liquid Glass'),
)
Use deformable: true for interactive squash and stretch feedback. Use
AppleLiquidStretchGestureMode.gestureDetector when the surface contains
buttons or other tappable Flutter children.
Native toast #
await AppleLiquidToast.show(
title: 'Added to Cart',
systemImage: 'cart.fill',
action: AppleLiquidToastAction(
title: 'Undo',
tintColor: const Color(0xFFFF9500),
dismissesToast: false,
onPressed: () {
AppleLiquidToast.show(
title: 'Removed From Cart',
systemImage: 'checkmark.circle.fill',
);
},
),
);
AppleLiquidToast.show returns false when no native iOS overlay can be
attached, including unsupported platforms. Native toasts require iOS 16 or
newer; iOS 26 uses the system Liquid Glass effect, while older supported iOS
versions use a native rounded fallback.
Native sheet #
AppleLiquidSheet presents a native iOS sheet from Flutter. The sheet renders a
Liquid Glass SwiftUI NavigationStack with Form content, a content-sized
detent, optional background zoom on the presenting page, and configurable
toolbar actions to dismiss.
Pass AppleLiquidSheetContent to customize the native form. The content model
supports sections plus text, value, button, toggle, picker, multi-picker,
segmented, slider, text-field, and nested navigation rows. Toggle, picker,
multi-picker, segmented, slider, and text-field state stays local to the native
sheet while it is presented. Omit step on AppleLiquidSheetRow.slider for a
continuous slider. Set
valuePlacement to AppleLiquidSheetSliderValuePlacement.besideTrack to show
the value beside the slider track instead of above it. Pass valueSuffix to
append a unit to the rendered value.
For multi-pickers, the selection summary appears at the trailing edge by
default. Set selectionLabelPlacement to
AppleLiquidSheetMultiPickerLabelPlacement.primary to use the current
selection as the primary row label while keeping title for the pushed picker
page:
const AppleLiquidSheetRow.multiPicker(
title: 'Kategorie',
options: <String>['Alle', 'Garten', 'Umzug'],
selectedOptions: <String>['Alle'],
systemImage: 'square.grid.2x2.fill',
chevronColor: Color(0xFF0A84FF),
selectionSystemImages: <String, String>{
'Alle': 'square.grid.2x2.fill',
'Garten': 'leaf.fill',
'Umzug': 'shippingbox.fill',
},
selectionLabelPlacement:
AppleLiquidSheetMultiPickerLabelPlacement.primary,
);
selectionSystemImages displays each mapped SF Symbol beside its option and
changes the main-row icon immediately when exactly one matching option is
selected. For multiple selections, or when no mapping exists, systemImage
remains the main-row fallback.
Set chevronColor on AppleLiquidSheetRow.picker, multiPicker, or
navigation to color only that row's navigation chevron. Omit it to preserve
the native SwiftUI chevron and system color.
| API | Purpose |
|---|---|
AppleLiquidSheetContent |
One native sheet page with title, optional detents, section backgrounds, section spacing, and sections |
AppleLiquidSheetToolbarAction |
Optional leading or trailing toolbar button with text and/or SF Symbol |
AppleLiquidSheetSection |
Optional section header with configurable color and content spacing, native form rows, and per-section background, border, and corner styling |
AppleLiquidSheetRow.text |
Title and optional subtitle |
AppleLiquidSheetRow.value |
Native label-value row |
AppleLiquidSheetRow.toggle |
Native SwiftUI toggle with local sheet state |
AppleLiquidSheetRow.picker |
Native picker row with local sheet state and an optional custom chevron color |
AppleLiquidSheetRow.multiPicker |
Native multi-selection picker with initial selections, configurable summary placement, custom chevron color, and a Dart change callback |
AppleLiquidSheetRow.segmented |
Two separate, equal-width native buttons with local selection state |
AppleLiquidSheetSegmentedStyle |
Per-row colors, dimensions, typography, spacing, borders, shadows, press feedback, and selection transitions |
AppleLiquidSheetRow.button |
Full-width native action button with a Dart callback, optional sheet dismissal, accessibility label, and configurable enabled state |
AppleLiquidSheetButtonStyle |
Button colors, dimensions, typography, alignment, form-row insets/background/separator, and press feedback |
AppleLiquidSheetRow.slider |
Native slider row with local sheet state, optional step, min/max, tint, value placement, and horizontal row inset |
AppleLiquidSheetRow.textField |
Native text field row with local sheet state |
AppleLiquidSheetRow.navigation |
Pushes another AppleLiquidSheetContent page with an optional custom chevron color |
AppleLiquidSheetDetents can be set on every AppleLiquidSheetContent, not
only the root sheet. Heights are native iOS points:
const AppleLiquidSheetContent(
title: 'Release',
detents: AppleLiquidSheetDetents(
initialHeight: 300,
expandedHeight: 520,
),
sections: <AppleLiquidSheetSection>[
AppleLiquidSheetSection(
rows: <AppleLiquidSheetRow>[
AppleLiquidSheetRow.value(title: 'Detents', value: 'Two-step'),
],
),
],
)
void openMap() {
// App-specific navigation or state update.
}
final AppleLiquidSheetContent content = AppleLiquidSheetContent(
title: 'Project',
showsSectionBackgrounds: false,
sectionSpacing: 8,
doneSemanticLabel: 'Close sheet',
leadingAction: AppleLiquidSheetToolbarAction(
systemImage: 'xmark',
semanticLabel: 'Dismiss sheet',
foregroundColor: Color(0xFFFF9F0A),
),
trailingAction: AppleLiquidSheetToolbarAction(
title: 'Apply',
systemImage: 'checkmark',
semanticLabel: 'Apply changes',
foregroundColor: Color(0xFFFFFFFF),
backgroundColor: Color(0xFF34C759),
),
detents: AppleLiquidSheetDetents(
initialHeight: 420,
expandedHeight: 640,
),
sections: <AppleLiquidSheetSection>[
AppleLiquidSheetSection(
title: 'Overview',
titleColor: Color(0xFFE6E6E6),
showsBackground: true,
backgroundColor: Color(0xFF1A1A1A),
borderColor: Color(0xFF2C2C2E),
cornerRadius: 14,
rows: <AppleLiquidSheetRow>[
AppleLiquidSheetRow.value(
title: 'Package',
value: 'mjn_liquid_ui',
systemImage: 'shippingbox.fill',
),
AppleLiquidSheetRow.toggle(
title: 'Background zoom',
value: true,
),
AppleLiquidSheetRow.picker(
title: 'Theme',
options: <String>['Auto', 'Light', 'Dark'],
selectedOption: 'Auto',
chevronColor: Color(0xFFFF9F0A),
),
AppleLiquidSheetRow.segmented(
title: 'Layout',
firstOption: 'List',
secondOption: 'Grid',
selectedOption: 'List',
),
AppleLiquidSheetRow.button(
title: 'Show on map',
systemImage: 'map',
tintColor: Color(0xFF007AFF),
semanticLabel: 'Open map view',
dismissesSheet: true,
style: const AppleLiquidSheetButtonStyle(
rowTopInset: 12,
rowBottomInset: 0,
cornerRadius: 12,
),
onPressed: openMap,
),
AppleLiquidSheetRow.slider(
title: 'Intensity',
value: 0.72,
valueSuffix: 'x',
tintColor: Color(0xFF0A84FF),
valuePlacement: AppleLiquidSheetSliderValuePlacement.besideTrack,
rowHorizontalInset: 8,
),
AppleLiquidSheetRow.navigation(
title: 'Details',
chevronColor: Color(0xFF34C759),
content: AppleLiquidSheetContent(
title: 'Details',
detents: AppleLiquidSheetDetents(
initialHeight: 300,
expandedHeight: 520,
),
sections: <AppleLiquidSheetSection>[
AppleLiquidSheetSection(
rows: <AppleLiquidSheetRow>[
AppleLiquidSheetRow.text(
title: 'Rendered natively',
subtitle: 'This page is a SwiftUI Form pushed from Dart.',
),
],
),
],
),
),
],
),
],
);
final bool didShow = await AppleLiquidSheet.showSheet(
backgroundZoomScale: 0.94,
sheetColor: const Color(0xFFEAF3FF),
content: content,
);
Sheet buttons do not contain app-specific behavior. onPressed is registered
for the active presentation and invoked in Dart when the native SwiftUI button
is tapped. dismissesSheet controls whether iOS closes the sheet after the
callback. Omit style for the blue outlined default, or pass
AppleLiquidSheetButtonStyle to configure colors, sizing, typography,
alignment, row insets, the form background and separator, disabled appearance,
and press feedback.
Button row spacing #
rowTopInset and rowBottomInset override the corresponding side of
rowVerticalInset. On iOS 17 and newer they describe the complete distance to
adjacent native form content. A value of 0 therefore adds no section gap:
AppleLiquidSheetContent(
sectionSpacing: 8,
sections: <AppleLiquidSheetSection>[
AppleLiquidSheetSection(
rows: <AppleLiquidSheetRow>[
AppleLiquidSheetRow.button(
title: 'Show on map',
systemImage: 'map.fill',
style: const AppleLiquidSheetButtonStyle(
rowTopInset: 12,
rowBottomInset: 0,
),
onPressed: openMap,
),
],
),
const AppleLiquidSheetSection(
title: 'Category',
rows: <AppleLiquidSheetRow>[
AppleLiquidSheetRow.text(title: 'Garden'),
],
),
],
);
Here the automatic space between the button and the Category section is
removed, even though the other section boundaries continue to use
sectionSpacing: 8. The section header keeps only its own intrinsic text
height. If the button is the final row of the final section,
rowBottomInset: 0 also removes the form's automatic bottom content margin.
In exact button-spacing mode, non-zero gaps are rendered as transparent rows at
the end of the preceding native Section. No additional Section is created,
so SwiftUI does not add another header or footer margin. The content-sized
detent includes the rendered section headers, rows, gaps, and sheet chrome up to
the final button row.
If only rowVerticalInset is set, it remains the fallback for both sides. On
iOS 16, SwiftUI keeps its native inter-section and bottom form spacing because
the required per-list spacing APIs are available from iOS 17.
AppleLiquidSheetRow.segmented requires exactly two distinct, non-empty
options. It renders them as separate, equal-width rounded buttons. Keep both
labels short so they fit comfortably next to each other.
Pass AppleLiquidSheetSegmentedStyle to customize colors, dimensions,
typography, spacing, borders, shadows, text scaling, press feedback, and the
selection transition for each row:
AppleLiquidSheetRow.segmented(
title: 'Sort by',
subtitle: 'Choose the result order',
firstOption: 'Newest',
secondOption: 'Oldest',
selectedOption: 'Newest',
style: const AppleLiquidSheetSegmentedStyle(
selectedBackgroundColor: Color(0x2634C759),
unselectedBackgroundColor: Color(0xFFE5E5EA),
selectedTextColor: Color(0xFF34C759),
unselectedTextColor: Color(0xFF1C1C1E),
selectedBorderColor: Color(0x9934C759),
unselectedBorderColor: Color(0x338E8E93),
selectedShadowColor: Color(0x0AFFFFFF),
titleColor: Color(0xFF111111),
subtitleColor: Color(0xFF666666),
buttonHeight: 50,
cornerRadius: 16,
buttonSpacing: 14,
contentSpacing: 12,
verticalPadding: 8,
rowHorizontalInset: 8,
borderWidth: 1,
selectedShadowRadius: 8,
selectedShadowOffsetX: 0,
selectedShadowOffsetY: 2,
titleFontSize: 18,
subtitleFontSize: 13,
buttonFontSize: 17,
titleFontWeight: AppleLiquidSheetSegmentedFontWeight.bold,
subtitleFontWeight: AppleLiquidSheetSegmentedFontWeight.regular,
buttonFontWeight: AppleLiquidSheetSegmentedFontWeight.semibold,
minimumTextScaleFactor: 0.7,
pressedScale: 0.97,
pressedOpacity: 0.82,
pressAnimationDuration: 0.14,
selectionAnimationEnabled: true,
selectionAnimationCurve: AppleLiquidSheetSegmentedAnimationCurve.easeInOut,
selectionAnimationDuration: 0.15,
),
)
Leave individual color or font-size values null to retain adaptive native iOS
colors and Dynamic Type sizing. Set borderWidth to 0 for borderless
buttons. Set selectionAnimationEnabled to false or
selectionAnimationDuration to 0 when the selected state should change
without a transition.
Use AppleLiquidSheetController when the calling code needs imperative control
or presentation state:
final AppleLiquidSheetController sheetController =
AppleLiquidSheetController(
backgroundZoomScale: 0.94,
sheetColor: null,
content: content,
);
final bool didShow = await sheetController.showSheet();
await sheetController.dismiss();
Wrap Flutter content behind the native sheet with
AppleLiquidSheetBackgroundInteractionGuard when the app wants the plugin to
manage background interaction while the sheet is active:
AppleLiquidSheetBackgroundInteractionGuard(
controller: sheetController,
absorbPointers: true,
lockScrolling: true,
lockedScrollPhysics: const NeverScrollableScrollPhysics(),
child: const YourPageContent(),
)
The guard is optional and configurable. Set absorbPointers or lockScrolling
to false to keep those behaviors under app control, provide a custom
lockedScrollPhysics, or use builder to react to the active sheet state with
app-specific UI.
Pass scrollContext from inside the presenting scrollable when calling
showSheet to stop active fling momentum before the native sheet opens:
await sheetController.showSheet(scrollContext: context);
backgroundZoomScale controls how far the presenting view scales back while
the sheet is open. Set sheetColor to force a specific sheet background color;
leave it null to use the native Liquid Glass/system presentation background.
The method returns true after a native iOS sheet was shown and dismissed. It
returns false on unsupported platforms so apps can present their own Flutter
fallback.
Repeated show calls return true without opening another sheet so fallback code
does not stack a second presentation.
The controller exposes isShowing and isShown for UI state while its
presentation is active.
Set showsSectionBackgrounds: false on an AppleLiquidSheetContent page to
remove the rounded native SwiftUI section boxes while keeping the form rows and
controls. The option can be configured independently for nested pages. Each
AppleLiquidSheetSection can inherit that setting or override it with
showsBackground, titleColor, titleSpacing, backgroundColor,
borderColor, cornerRadius, titleHorizontalInset, titleLeadingInset, and
titleTrailingInset.
These options work with every supported sheet row type. To style only one
element, place that row in its own AppleLiquidSheetSection, as shown by the
Label field in the example app.
Use titleSpacing to set the absolute distance between a section's native title
and first content row. The value is measured in native iOS points. Set it to 0
to remove the gap completely, or omit it to keep SwiftUI's system spacing:
const AppleLiquidSheetSection(
title: 'Kategorie',
titleColor: Color(0xFFE6E6E6),
titleHorizontalInset: 8,
titleSpacing: 0,
rows: <AppleLiquidSheetRow>[
AppleLiquidSheetRow.text(title: 'Garten'),
],
)
titleHorizontalInset and the slider row's rowHorizontalInset are absolute
native iOS point values. Use titleLeadingInset/titleTrailingInset or
rowLeadingInset/rowTrailingInset to override only one edge. A directional
value takes precedence over the corresponding horizontal value. Set an inset
to 0 to align that edge with its section, or omit all applicable values to
preserve SwiftUI's system inset:
const AppleLiquidSheetRow.slider(
title: 'Distanz',
value: 5,
min: 0,
max: 10,
rowLeadingInset: 8,
rowTrailingInset: 24,
)
Set sectionSpacing on an AppleLiquidSheetContent page to control the native
vertical distance between its form sections. When omitted, SwiftUI keeps its
system spacing. Custom section spacing is applied on iOS 17 and newer. The
example app demonstrates 8 points on the main sheet page and 24 points on
the nested Release page.
const AppleLiquidSheetContent(
title: 'Main sheet',
sectionSpacing: 8,
sections: <AppleLiquidSheetSection>[
AppleLiquidSheetSection(
title: 'Overview',
rows: <AppleLiquidSheetRow>[
AppleLiquidSheetRow.navigation(
title: 'Release details',
content: AppleLiquidSheetContent(
title: 'Release',
sectionSpacing: 24,
sections: <AppleLiquidSheetSection>[
AppleLiquidSheetSection(
title: 'Highlights',
rows: <AppleLiquidSheetRow>[
AppleLiquidSheetRow.text(title: 'Native SwiftUI form'),
],
),
AppleLiquidSheetSection(
title: 'Spacing demo',
rows: <AppleLiquidSheetRow>[
AppleLiquidSheetRow.value(
title: 'Section spacing',
value: '24 pt',
),
],
),
],
),
),
],
),
],
);
The iOS implementation opens at a SwiftUI content-sized sheet detent instead of
expanding straight to .large. Pass AppleLiquidSheetDetents to any
AppleLiquidSheetContent to customize the starting height and optional second
higher height for that page, including nested navigation pages. When no custom
detents are supplied and the estimated active form content is taller than the
normal sheet height, iOS also gets an automatic second higher detent so the user
can pull the sheet up. The form scroll background stays hidden so the Liquid
Glass sheet remains visible behind the form content.
showTemplateSheet() and AppleLiquidSheetController.showTemplateSheet() are
kept as compatibility aliases for older code.
The earlier bottom search bar option has been removed from the sheet API.
iOS notes #
- The iOS implementation uses Swift, SwiftUI, UIKit, and Flutter platform views.
- The tab bar's search segment is implemented with native SwiftUI
Tab(..., role: .search). - SF Symbols names are passed through
systemImagefor tab items and throughAppleLiquidSymbol.namefor standalone symbols. - SF Symbol weights are optional and use native
UIImage.SymbolConfigurationfor standalone symbols. Tabbar weights are passed to native tab labels and may still be influenced by iOS tab bar styling. - Liquid Glass appearance depends on the iOS and Xcode versions used to build and run the app. Unsupported iOS versions may display a simpler fallback surface.
- Changes inside
ios/Classesare native Swift/SwiftUI changes. Flutter hot restart does not recompile those files; stop and rerun the example app so Xcode performs an incremental iOS build.
Limitations #
- iOS is the only officially supported platform for this release.
- Android, web, and desktop are not officially supported yet.
- Unsupported-platform fallbacks are intentionally simple and experimental.
- The plugin does not provide an Android native implementation yet.
- The search tab is a native search-role segment; app-specific search UI should be implemented by your Flutter page content.
Example #
Run the bundled example app on an iOS simulator:
cd example
flutter run -d <ios-simulator-id>
The example demonstrates the liquid tab bar, standalone SF Symbols, search segment, switch, slider, liquid glass surface, and native settings sheet.
License #
This package is licensed under the BSD 3-Clause License. See LICENSE.







