ios_liquid_glass 0.9.8 copy "ios_liquid_glass: ^0.9.8" to clipboard
ios_liquid_glass: ^0.9.8 copied to clipboard

PlatformiOS

Native iOS 26 Liquid Glass (UIGlassEffect and UITabBarController) for Flutter, plus an Impeller liquid-lens tier for iridescent droplet navigation.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:ios_liquid_glass/ios_liquid_glass.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await LiquidGlassNativeTabShell.instance.initialize();
  runApp(const LiquidGlassExampleApp());
}

class LiquidGlassExampleApp extends StatelessWidget {
  const LiquidGlassExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Liquid Glass Kit',
      theme: ThemeData(
        colorSchemeSeed: const Color(0xFF015488),
        useMaterial3: true,
      ),
      // LiquidGlassTabScaffold listens to this automatically: the native
      // tab bar hides on any pushed route (e.g. DetailsPage below) without
      // extra wiring, because ModalRoute.isCurrent is checked post-frame.
      navigatorObservers: [LiquidGlassNavigatorObserver.instance],
      debugShowCheckedModeBanner: false,
      home: const RootTabs(),
    );
  }
}

const List<_TabSpec> _tabs = [
  _TabSpec(
    title: 'Home',
    systemImage: 'house',
    selectedSystemImage: 'house.fill',
    icon: Icons.home_outlined,
    selectedIcon: Icons.home,
  ),
  _TabSpec(
    title: 'Search',
    systemImage: 'magnifyingglass',
    icon: Icons.search_outlined,
    selectedIcon: Icons.search,
  ),
  _TabSpec(
    title: 'Favorites',
    systemImage: 'heart',
    selectedSystemImage: 'heart.fill',
    icon: Icons.favorite_outline,
    selectedIcon: Icons.favorite,
  ),
  _TabSpec(
    title: 'Profile',
    systemImage: 'person.circle',
    selectedSystemImage: 'person.circle.fill',
    icon: Icons.person_outline,
    selectedIcon: Icons.person,
  ),
];

class _TabSpec {
  const _TabSpec({
    required this.title,
    required this.icon,
    required this.selectedIcon,
    this.systemImage,
    this.selectedSystemImage,
  });

  final String title;
  final String? systemImage;
  final String? selectedSystemImage;
  final IconData icon;
  final IconData selectedIcon;
}

class RootTabs extends StatefulWidget {
  const RootTabs({super.key});

  @override
  State<RootTabs> createState() => _RootTabsState();
}

class _RootTabsState extends State<RootTabs> {
  int _currentIndex = 0;
  bool _barVisible = true;
  bool _favoritesBadgeOn = true;

  bool get _usesNativeTabBar => LiquidGlassPlatform.supportsNativeTabShell;

  List<LiquidGlassTabDestination> get _destinations => [
    LiquidGlassTabDestination(
      title: _tabs[0].title,
      systemImage: _tabs[0].systemImage,
      selectedSystemImage: _tabs[0].selectedSystemImage,
      icon: Icon(_tabs[0].icon),
      selectedIcon: Icon(_tabs[0].selectedIcon),
      page: _HomeTab(
        barVisible: _barVisible,
        onToggleBar: () => setState(() => _barVisible = !_barVisible),
      ),
    ),
    LiquidGlassTabDestination(
      title: _tabs[1].title,
      systemImage: _tabs[1].systemImage,
      selectedSystemImage: _tabs[1].selectedSystemImage,
      icon: Icon(_tabs[1].icon),
      selectedIcon: Icon(_tabs[1].selectedIcon),
      page: const _SearchTab(),
    ),
    LiquidGlassTabDestination(
      title: _tabs[2].title,
      systemImage: _tabs[2].systemImage,
      selectedSystemImage: _tabs[2].selectedSystemImage,
      icon: Icon(_tabs[2].icon),
      selectedIcon: Icon(_tabs[2].selectedIcon),
      badge: _favoritesBadgeOn ? '3' : null,
      page: _FavoritesTab(
        badgeOn: _favoritesBadgeOn,
        onToggleBadge: () =>
            setState(() => _favoritesBadgeOn = !_favoritesBadgeOn),
      ),
    ),
    LiquidGlassTabDestination(
      title: _tabs[3].title,
      systemImage: _tabs[3].systemImage,
      selectedSystemImage: _tabs[3].selectedSystemImage,
      icon: Icon(_tabs[3].icon),
      selectedIcon: Icon(_tabs[3].selectedIcon),
      page: const _ProfileTab(),
    ),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      extendBody: true,
      extendBodyBehindAppBar: true,
      appBar: AppBar(
        title: Text('Liquid Glass · ${_tabs[_currentIndex].title}'),
        backgroundColor: Colors.transparent,
        foregroundColor: Colors.white,
        titleTextStyle: const TextStyle(
          color: Colors.white,
          fontSize: 20,
          fontWeight: FontWeight.w700,
          shadows: [Shadow(color: Colors.black26, blurRadius: 8)],
        ),
      ),
      body: LiquidGlassTabScaffold(
        destinations: _destinations,
        currentIndex: _currentIndex,
        onIndexChanged: (index) => setState(() => _currentIndex = index),
        barVisible: _barVisible,
      ),
      floatingActionButton: FloatingActionButton(
        backgroundColor: const Color(0xFF0A84FF),
        foregroundColor: Colors.white,
        onPressed: () => _showSnack(context, 'FAB tapped'),
        child: const Icon(Icons.edit),
      ),
      floatingActionButtonLocation: _usesNativeTabBar
          ? const LiquidGlassNativeTabShellFabLocation()
          : FloatingActionButtonLocation.endFloat,
    );
  }
}

void _showSnack(BuildContext context, String message) {
  ScaffoldMessenger.of(context).showSnackBar(
    SnackBar(content: Text(message), behavior: SnackBarBehavior.floating),
  );
}

/// Vivid gradient + soft orbs behind every tab — glass is transparent and
/// only reads as "glass" over detailed content.
class _GlassBackdrop extends StatelessWidget {
  const _GlassBackdrop({required this.child});

  final Widget child;

  @override
  Widget build(BuildContext context) {
    return DecoratedBox(
      decoration: const BoxDecoration(
        gradient: LinearGradient(
          begin: Alignment.topLeft,
          end: Alignment.bottomRight,
          colors: [
            Color(0xFF3A0CA3),
            Color(0xFF7209B7),
            Color(0xFFB5179E),
            Color(0xFF4CC9F0),
          ],
          stops: [0.0, 0.35, 0.65, 1.0],
        ),
      ),
      child: Stack(children: [const _BackdropShapes(), child]),
    );
  }
}

Text _sectionTitle(String text) => Text(
  text.toUpperCase(),
  style: const TextStyle(
    color: Colors.white70,
    fontWeight: FontWeight.w700,
    fontSize: 13,
    letterSpacing: 1.4,
    shadows: [Shadow(color: Colors.black26, blurRadius: 6)],
  ),
);

const TextStyle _buttonLabel = TextStyle(
  color: Colors.white,
  fontWeight: FontWeight.w600,
  shadows: [Shadow(color: Colors.black38, blurRadius: 8, offset: Offset(0, 1))],
);

const LiquidGlassStyle _clearStyle = LiquidGlassStyle(
  nativeVariant: NativeGlassVariant.clear,
);

const LiquidGlassStyle _tintedStyle = LiquidGlassStyle(
  nativeTintColor: Color(0xFF0A84FF),
  plainColor: Color(0xFF0A84FF),
);

const LiquidGlassStyle _darkStyle = LiquidGlassStyle(
  nativeTintColor: Colors.black,
  plainColor: Color(0xFF1C1C1E),
);

EdgeInsets _tabPadding(BuildContext context) => EdgeInsets.fromLTRB(
  24,
  MediaQuery.paddingOf(context).top + 72,
  24,
  LiquidGlassTabScaffold.contentBottomClearance(context) + 16,
);

class _HomeTab extends StatelessWidget {
  const _HomeTab({required this.barVisible, required this.onToggleBar});

  final bool barVisible;
  final VoidCallback onToggleBar;

  @override
  Widget build(BuildContext context) {
    return _GlassBackdrop(
      child: ListView(
        padding: _tabPadding(context),
        children: [
          Align(
            alignment: Alignment.centerLeft,
            child: LiquidGlassContainer(
              borderRadius: 20,
              padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
              child: Row(
                mainAxisSize: MainAxisSize.min,
                children: [
                  Container(
                    width: 8,
                    height: 8,
                    decoration: BoxDecoration(
                      shape: BoxShape.circle,
                      color: LiquidGlassPlatform.supportsNativeGlass
                          ? const Color(0xFF30D158)
                          : Colors.orangeAccent,
                    ),
                  ),
                  const SizedBox(width: 8),
                  Text(
                    LiquidGlassPlatform.supportsNativeGlass
                        ? 'Native glass · iOS ${LiquidGlassPlatform.osMajorVersion}'
                        : 'Plain fallback',
                    style: _buttonLabel.copyWith(fontSize: 13),
                  ),
                ],
              ),
            ),
          ),
          const SizedBox(height: 32),
          _sectionTitle('Buttons'),
          const SizedBox(height: 12),
          Wrap(
            spacing: 12,
            runSpacing: 12,
            children: [
              LiquidGlassButton(
                onPressed: () => _showSnack(context, 'Regular tapped'),
                child: const Text('Regular', style: _buttonLabel),
              ),
              LiquidGlassButton(
                style: _clearStyle,
                onPressed: () => _showSnack(context, 'Clear tapped'),
                child: const Text('Clear', style: _buttonLabel),
              ),
              LiquidGlassButton(
                style: _tintedStyle,
                onPressed: () => _showSnack(context, 'Tinted tapped'),
                child: const Text('Tinted', style: _buttonLabel),
              ),
              LiquidGlassButton(
                style: _darkStyle,
                onPressed: () => _showSnack(context, 'Dark tapped'),
                child: const Text('Dark', style: _buttonLabel),
              ),
              const LiquidGlassButton(
                onPressed: null,
                child: Text('Disabled', style: _buttonLabel),
              ),
            ],
          ),
          const SizedBox(height: 28),
          _sectionTitle('Icon buttons'),
          const SizedBox(height: 12),
          Wrap(
            spacing: 14,
            runSpacing: 14,
            crossAxisAlignment: WrapCrossAlignment.center,
            children: [
              LiquidGlassIconButton(
                icon: const Icon(Icons.favorite),
                iconColor: Colors.white,
                onPressed: () => _showSnack(context, 'Favorite tapped'),
              ),
              LiquidGlassIconButton(
                icon: const Icon(Icons.share),
                iconColor: Colors.white,
                style: _clearStyle,
                onPressed: () => _showSnack(context, 'Share tapped'),
              ),
              LiquidGlassIconButton(
                icon: const Icon(Icons.add),
                iconColor: Colors.white,
                style: _tintedStyle,
                size: 60,
                iconSize: 28,
                onPressed: () => _showSnack(context, 'Add tapped'),
              ),
              LiquidGlassIconButton(
                icon: const Icon(Icons.settings),
                iconColor: Colors.white,
                style: _darkStyle,
                onPressed: () => _showSnack(context, 'Settings tapped'),
              ),
            ],
          ),
          const SizedBox(height: 28),
          _sectionTitle('Navigation'),
          const SizedBox(height: 12),
          Wrap(
            spacing: 12,
            runSpacing: 12,
            children: [
              LiquidGlassButton(
                height: 44,
                padding: const EdgeInsets.symmetric(horizontal: 18),
                style: _darkStyle,
                onPressed: () => Navigator.of(context).push<void>(
                  MaterialPageRoute(builder: (_) => const DetailsPage()),
                ),
                child: const Text(
                  'Push details page',
                  style: TextStyle(
                    color: Colors.white,
                    fontWeight: FontWeight.w600,
                    fontSize: 14,
                  ),
                ),
              ),
              LiquidGlassButton(
                height: 44,
                padding: const EdgeInsets.symmetric(horizontal: 18),
                style: _darkStyle,
                onPressed: onToggleBar,
                child: Text(
                  barVisible ? 'Hide bar' : 'Show bar',
                  style: const TextStyle(
                    color: Colors.white,
                    fontWeight: FontWeight.w600,
                    fontSize: 14,
                  ),
                ),
              ),
            ],
          ),
        ],
      ),
    );
  }
}

/// Pushed on top of [RootTabs]. LiquidGlassTabScaffold checks
/// `ModalRoute.of(context).isCurrent` on every navigator notification, so
/// the native tab bar hides for as long as this page is on top — no
/// per-route bookkeeping required in the host app.
class DetailsPage extends StatelessWidget {
  const DetailsPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      extendBodyBehindAppBar: true,
      appBar: AppBar(
        title: const Text('Details'),
        backgroundColor: Colors.transparent,
        foregroundColor: Colors.white,
      ),
      body: _GlassBackdrop(
        child: Center(
          child: Padding(
            padding: const EdgeInsets.all(32),
            child: LiquidGlassContainer(
              borderRadius: 24,
              padding: const EdgeInsets.all(24),
              child: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  const Text(
                    'The native tab bar hid the moment this route pushed, '
                    'and will reappear on pop — driven entirely by '
                    'LiquidGlassNavigatorObserver.',
                    textAlign: TextAlign.center,
                    style: _buttonLabel,
                  ),
                  const SizedBox(height: 20),
                  LiquidGlassButton(
                    style: _tintedStyle,
                    onPressed: () => Navigator.of(context).pop(),
                    child: const Text('Close', style: _buttonLabel),
                  ),
                ],
              ),
            ),
          ),
        ),
      ),
    );
  }
}

class _SearchTab extends StatelessWidget {
  const _SearchTab();

  @override
  Widget build(BuildContext context) {
    const results = ['Liquid Glass', 'UIGlassEffect', 'UITabBarController', 'Impeller'];
    return _GlassBackdrop(
      child: ListView(
        padding: _tabPadding(context),
        children: [
          LiquidGlassContainer(
            borderRadius: 16,
            padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
            child: Row(
              children: const [
                Icon(Icons.search, color: Colors.white70),
                SizedBox(width: 10),
                Text('Search', style: _buttonLabel),
              ],
            ),
          ),
          const SizedBox(height: 20),
          _sectionTitle('Results'),
          const SizedBox(height: 12),
          for (final r in results) ...[
            LiquidGlassContainer(
              borderRadius: 14,
              padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
              child: Text(r, style: _buttonLabel),
            ),
            const SizedBox(height: 10),
          ],
        ],
      ),
    );
  }
}

class _FavoritesTab extends StatelessWidget {
  const _FavoritesTab({required this.badgeOn, required this.onToggleBadge});

  final bool badgeOn;
  final VoidCallback onToggleBadge;

  @override
  Widget build(BuildContext context) {
    const favorites = ['Frosted card', 'Droplet nav', 'Adaptive theme'];
    return _GlassBackdrop(
      child: ListView(
        padding: _tabPadding(context),
        children: [
          LiquidGlassButton(
            height: 44,
            padding: const EdgeInsets.symmetric(horizontal: 18),
            style: _darkStyle,
            onPressed: onToggleBadge,
            child: Text(
              badgeOn ? 'Clear badge' : 'Set badge',
              style: const TextStyle(
                color: Colors.white,
                fontWeight: FontWeight.w600,
                fontSize: 14,
              ),
            ),
          ),
          const SizedBox(height: 20),
          _sectionTitle('Favorites'),
          const SizedBox(height: 12),
          for (final f in favorites) ...[
            LiquidGlassContainer(
              borderRadius: 14,
              padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
              child: Row(
                children: [
                  const Icon(Icons.favorite, color: Colors.white, size: 18),
                  const SizedBox(width: 10),
                  Text(f, style: _buttonLabel),
                ],
              ),
            ),
            const SizedBox(height: 10),
          ],
        ],
      ),
    );
  }
}

class _ProfileTab extends StatelessWidget {
  const _ProfileTab();

  @override
  Widget build(BuildContext context) {
    return _GlassBackdrop(
      child: ListView(
        padding: _tabPadding(context),
        children: [
          Center(
            child: LiquidGlassContainer(
              borderRadius: 28,
              padding: const EdgeInsets.all(24),
              child: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  const CircleAvatar(
                    radius: 36,
                    backgroundColor: Color(0x33FFFFFF),
                    child: Icon(Icons.person, color: Colors.white, size: 36),
                  ),
                  const SizedBox(height: 14),
                  const Text(
                    'Liquid Glass Kit',
                    style: TextStyle(
                      color: Colors.white,
                      fontWeight: FontWeight.w700,
                      fontSize: 18,
                    ),
                  ),
                  const SizedBox(height: 4),
                  Text(
                    LiquidGlassPlatform.supportsNativeGlass
                        ? 'iOS ${LiquidGlassPlatform.osMajorVersion} · native tier'
                        : 'plain tier',
                    style: const TextStyle(color: Colors.white70, fontSize: 13),
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }
}

/// Soft glowing orbs plus a couple of crisp accents behind the glass — the
/// material needs detail to refract, but the shapes stay out of the
/// content's way and blend with the gradient instead of fighting it.
class _BackdropShapes extends StatelessWidget {
  const _BackdropShapes();

  @override
  Widget build(BuildContext context) {
    Widget orb(double size, Color color, {double opacity = 0.55}) => Container(
      width: size,
      height: size,
      decoration: BoxDecoration(
        shape: BoxShape.circle,
        gradient: RadialGradient(
          colors: [color.withValues(alpha: opacity), color.withValues(alpha: 0)],
          stops: const [0.15, 1.0],
        ),
      ),
    );

    Widget ring(double size, Color color) => Container(
      width: size,
      height: size,
      decoration: BoxDecoration(
        shape: BoxShape.circle,
        border: Border.all(color: color.withValues(alpha: 0.8), width: 10),
      ),
    );

    return Positioned.fill(
      child: Stack(
        children: [
          Positioned(top: -60, left: -80, child: orb(320, Colors.white, opacity: 0.7)),
          Positioned(top: 260, right: -120, child: orb(320, const Color(0xFF4CC9F0), opacity: 0.9)),
          Positioned(top: 560, left: -100, child: orb(300, const Color(0xFFFF8FAB), opacity: 0.7)),
          Positioned(top: 820, right: -60, child: orb(260, const Color(0xFFFFD166), opacity: 0.6)),
          Positioned(top: 130, right: -30, child: ring(90, Colors.white)),
          Positioned(top: 420, right: -40, child: ring(70, const Color(0xFFFFD166))),
          Positioned(bottom: 90, left: -40, child: ring(80, const Color(0xFF80FFDB))),
        ],
      ),
    );
  }
}
1
likes
150
points
326
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Native iOS 26 Liquid Glass (UIGlassEffect and UITabBarController) for Flutter, plus an Impeller liquid-lens tier for iridescent droplet navigation.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

flutter, liquid_glass_easy, plugin_platform_interface

More

Packages that depend on ios_liquid_glass

Packages that implement ios_liquid_glass