Liquid Glass Bottom Nav Water Effect

A reusable Flutter bottom navigation bar with a draggable liquid-glass thumb, adaptive device performance, independent SafeArea controls, RTL support, haptic feedback, and no dependency on routing or state-management packages.

Demo

Liquid Glass Bottom Navigation Water Effect Demo

Liquid Glass Bottom Navigation Demo    Adaptive Performance Demo

Features

  • Full liquid-glass quality by default
  • Optional automatic quality selection based on real Flutter frame timings
  • Manual quality, balanced, and performance overrides
  • Separate SafeArea controls for top, bottom, left, and right
  • Controlled selected index
  • Custom labels and any icon widgets
  • Optional active icons
  • Tap and drag selection
  • RTL support
  • Light and dark theme support
  • Configurable colors, dimensions, text styles, blur, and animations
  • No continuous idle repainting in balanced and performance modes
  • Works with Bloc, Cubit, Provider, Riverpod, setState, AutoRoute, GoRouter, and other architectures

Installation

flutter pub add liquid_glass_bottom_nav_water_effect
dependencies:
  liquid_glass_bottom_nav_water_effect: ^1.3.0

Basic usage

import 'package:liquid_glass_bottom_nav_water_effect/liquid_glass_bottom_nav_water_effect.dart';

Scaffold(
  // Required when screen content should remain visible behind the glass.
  extendBody: true,
  body: yourScreenContent,
  bottomNavigationBar: LiquidGlassBottomNavBar(
    currentIndex: currentIndex,
    onItemSelected: (index) {
      setState(() => currentIndex = index);
    },
    items: const [
      LiquidGlassBottomNavItem(
        label: 'Home',
        icon: Icon(Icons.home_outlined),
        activeIcon: Icon(Icons.home_rounded),
      ),
      LiquidGlassBottomNavItem(
        label: 'Settings',
        icon: Icon(Icons.settings_outlined),
        activeIcon: Icon(Icons.settings_rounded),
      ),
    ],
  ),
)

performanceMode defaults to quality, and bottom SafeArea is enabled by default because this widget is normally used as a bottom navigation bar. The default glass background uses a translucent tint of primaryLiquidColor instead of a black or white fill.

Use Scaffold(extendBody: true) to place screen content behind the navigation bar. Without it, the bar remains translucent but Flutter reserves an empty area below the page body, so there is no screen content behind the glass to reveal.

Automatic device performance

Enable automatic performance adaptation explicitly:

LiquidGlassBottomNavBar(
  performanceMode: LiquidGlassPerformanceMode.automatic,
  currentIndex: currentIndex,
  items: items,
  onItemSelected: onItemSelected,
)

The package does not depend only on a device model name. It samples actual Flutter build and raster frame timings, so it can handle:

  • Older flagship devices that still perform well
  • Newer low-end devices
  • Tablets and large displays
  • Devices affected by thermal throttling or heavy screen content

In profile and release builds, automatic mode resolves to:

Resolved mode Rendering behavior Typical device state
quality Full blur, animated liquid waves, idle animation Smooth/high-capability
balanced Reduced blur, no continuous idle repaint Medium/default production
performance No backdrop blur or liquid waves Slow, busy, or battery-sensitive

Debug builds cannot classify hardware reliably because debug frame timings include development overhead. They therefore use the configured initialMode, which defaults to quality, while adaptive switching runs in profile/release.

You can observe the resolved mode:

LiquidGlassBottomNavBar(
  currentIndex: currentIndex,
  items: items,
  onItemSelected: onItemSelected,
  onResolvedPerformanceModeChanged: (mode) {
    debugPrint('Liquid glass mode: ${mode.name}');
  },
)

Quality-friendly automatic defaults

Version 1.2.0 makes automatic mode less strict:

  • Uses the slower of build and raster duration instead of adding both stages
  • Allows a small 8% jank ratio while still selecting quality
  • Selects quality at an average frame-pipeline time up to 16ms
  • Selects performance only from 24ms average or more than 20% jank
  • Uses the configured initialMode in debug, which defaults to quality

This favors the full visual effect on capable devices while still protecting slower devices when sustained frame pressure is detected.

Customize automatic detection

LiquidGlassBottomNavBar(
  currentIndex: currentIndex,
  items: items,
  onItemSelected: onItemSelected,
  performanceMode: LiquidGlassPerformanceMode.automatic,
  adaptivePerformanceConfig: const LiquidGlassAdaptivePerformanceConfig(
    initialMode: LiquidGlassPerformanceMode.quality,
    sampleFrameCount: 36,
    upgradeAverageFrameTime: Duration(milliseconds: 16),
    downgradeAverageFrameTime: Duration(milliseconds: 24),
    jankFrameTime: Duration(milliseconds: 32),
    maximumQualityJankRatio: 0.08,
    maximumJankRatio: 0.20,
    minimumModeDuration: Duration(seconds: 4),
    allowQualityMode: true,
    allowPerformanceMode: true,
  ),
)

Force a mode manually

Manual mode disables automatic detection:

performanceMode: LiquidGlassPerformanceMode.quality,

Other options:

performanceMode: LiquidGlassPerformanceMode.balanced,
performanceMode: LiquidGlassPerformanceMode.performance,

SafeArea control

Bottom only — default

LiquidGlassBottomNavBar(
  safeAreaTop: false,
  safeAreaBottom: true,
  currentIndex: currentIndex,
  items: items,
  onItemSelected: onItemSelected,
)

Top only

LiquidGlassBottomNavBar(
  safeAreaTop: true,
  safeAreaBottom: false,
  currentIndex: currentIndex,
  items: items,
  onItemSelected: onItemSelected,
)

Top and bottom

LiquidGlassBottomNavBar(
  safeAreaTop: true,
  safeAreaBottom: true,
  currentIndex: currentIndex,
  items: items,
  onItemSelected: onItemSelected,
)

Disable SafeArea completely

LiquidGlassBottomNavBar(
  safeAreaTop: false,
  safeAreaBottom: false,
  safeAreaLeft: false,
  safeAreaRight: false,
  currentIndex: currentIndex,
  items: items,
  onItemSelected: onItemSelected,
)

The old useSafeArea parameter remains temporarily available for backward compatibility but is deprecated.

Using SVG icons

The package accepts any Widget, so your app can use flutter_svg without forcing that dependency on every package consumer:

LiquidGlassBottomNavItem(
  label: context.l10n.home,
  icon: SvgPicture.asset(
    'assets/icons/home.svg',
    colorFilter: const ColorFilter.mode(Colors.black, BlendMode.srcIn),
  ),
)

Custom style

LiquidGlassBottomNavBar(
  currentIndex: currentIndex,
  onItemSelected: onItemSelected,
  items: items,
  style: const LiquidGlassBottomNavStyle(
    height: 80,
    borderRadius: 40,
    primaryLiquidColor: Color(0xFF6C63FF),
    secondaryLiquidColor: Color(0xFF9A95FF),
    backgroundColor: Color(0x246C63FF),
    darkBackgroundColor: Color(0x386C63FF),
    selectedItemColor: Colors.white,
    unselectedItemColor: Colors.white70,
  ),
)

Glass controls

LiquidGlassBottomNavBar(
  currentIndex: currentIndex,
  items: items,
  onItemSelected: onItemSelected,
  enableGlassEffect: true,
  enableWobble: true,
  showLabels: true,
  style: const LiquidGlassBottomNavStyle(
    glassOpacity: 0.025,
    darkGlassOpacity: 0.045,
    barBlurSigma: 6,
    highlightColor: Colors.white,
    highlightIntensity: 0.85,
    thumbShape: LiquidGlassThumbShape.capsule,
  ),
)

The default indicator shape is capsule. The other available shapes are liquid and circle.

Gradient glass

style: const LiquidGlassBottomNavStyle(
  backgroundGradient: LinearGradient(
    colors: [Color(0x336C63FF), Color(0x2218A0FB)],
  ),
  darkBackgroundGradient: LinearGradient(
    colors: [Color(0x446C63FF), Color(0x3318A0FB)],
  ),
),

Normal container without glass

LiquidGlassBottomNavBar(
  enableGlassEffect: false,
  currentIndex: currentIndex,
  items: items,
  onItemSelected: onItemSelected,
  style: const LiquidGlassBottomNavStyle(
    solidBackgroundColor: Color(0xFFF4F1FA),
    darkSolidBackgroundColor: Color(0xFF211D2A),
    thumbShape: LiquidGlassThumbShape.capsule,
    highlightIntensity: 0,
  ),
)

Use showLabels: false for icons only and enableWobble: false for a stable selection indicator.

Using it with Cubit

BlocBuilder<BottomNavCubit, BottomNavState>(
  builder: (context, state) {
    return LiquidGlassBottomNavBar(
      currentIndex: state.currentIndex,
      onItemSelected: context.read<BottomNavCubit>().changeIndex,
      items: items,
    );
  },
)

Performance testing

Test automatic detection on a real device in profile mode:

cd example
flutter run --profile

Use Flutter DevTools Performance view while dragging between destinations and scrolling content behind the navigation bar.