native_liquid_glass_flutter 0.0.3 copy "native_liquid_glass_flutter: ^0.0.3" to clipboard
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 UIVisualEffectView material 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

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]

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 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.
  • tintColor and tintOpacity: 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 LiquidGlassConfiguration stable; do not recreate highly customized platform-view surfaces every animation frame.
  • For live sliders, update lightweight preview state in onChanged and persist expensive work in onChangeEnd.
  • Keep slider step reasonable. Very small steps can produce excessive state updates if the host app performs heavy work during onChanged.
  • 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 LiquidGlassScaffold for 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.

  1. A Flutter widget resolves LiquidGlassNativePolicy and decides whether to render a Flutter fallback or create an iOS platform view.
  2. 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, and locale.
  3. The Swift platform view parses that map, applies semantic direction and accessibility language to the UIKit view, and configures the native control.
  4. Native user actions call back through the per-view MethodChannel; Flutter updates app state and sends a new configuration back when needed.
  5. didChangeDependencies and didUpdateWidget resync mounted native views, so theme, direction, and language changes rebuild native-side configuration without recreating the whole screen.
  6. 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.dart and use the shared environment configuration for isDark, isRtl, and locale.
  • Sync configuration from didChangeDependencies when 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:

1
likes
0
points
244
downloads

Documentation

Documentation

Publisher

unverified uploader

Weekly Downloads

Native iOS Liquid Glass surfaces, controls, overlays, menus, and adaptive Flutter fallbacks for Flutter apps.

Repository (GitHub)
View/report issues

Topics

#ios #liquid-glass #native #ui

License

unknown (license)

Dependencies

flutter

More

Packages that depend on native_liquid_glass_flutter

Packages that implement native_liquid_glass_flutter