liquid_tab_bar 2.0.0
liquid_tab_bar: ^2.0.0 copied to clipboard
An iOS 26-style floating liquid-glass tab bar for Flutter with a deformable droplet, refraction, search, actions, badges, and scroll-aware folding.
liquid_tab_bar #
A floating glass tab bar for Flutter. It supports a moving selection lens, search, action buttons, badges, and a compact shape while scrolling.
Package version in this repository: 2.0.0
iOS simulator preview #
Current normal styles on an iPhone 17 Pro Max simulator. Both images show the same tab bar with a selected tab and a notification badge.
| Light | Dark |
|---|---|
![]() |
![]() |
Features #
- Animated selection: Tap or drag across tabs; the glass lens follows and settles on the selected tab.
- Glass with fallbacks: Uses shader glass where supported, backdrop blur elsewhere, and an opaque mode when needed.
- Search and actions: Add an expandable search field or a separate action button beside the tabs.
- Icons and badges: Use Flutter icons or your own widgets, plus unread dots, counts, or text badges.
- Scroll folding: The bar can shrink to a small pill while you scroll and expand again when you return.
- Accessibility: Supports screen readers, right-to-left layouts, and reduced motion settings.
Installation #
For a published 2.0.0 release, add this to your app's pubspec.yaml:
dependencies:
liquid_tab_bar: ^2.0.0
To try this repository before that version is published, use a local path instead. Adjust the path to where you cloned this repository:
dependencies:
liquid_tab_bar:
path: ../liquid_tab_bar
Then run flutter pub get. Import the package in your Dart file:
import 'package:liquid_tab_bar/liquid_tab_bar.dart';
The glass shaders are bundled with the package. Your app does not need to declare them as assets.
Note
SVG support is optional. If you use SVG icons, add an SVG package such as
flutter_svg to your app. Standard Flutter icons work without it.
Quick Start #
This complete lib/main.dart example shows three tabs and a scrollable page:
import 'package:flutter/material.dart';
import 'package:liquid_tab_bar/liquid_tab_bar.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await LiquidGlass.load();
runApp(const DemoApp());
}
class DemoApp extends StatefulWidget {
const DemoApp({super.key});
@override
State<DemoApp> createState() => _DemoAppState();
}
class _DemoAppState extends State<DemoApp> {
int selectedIndex = 0;
@override
Widget build(BuildContext context) {
const tabLabels = ['Home', 'Explore', 'Profile'];
return MaterialApp(
home: LiquidTabBarScaffold(
appBar: AppBar(title: const Text('Liquid Tab Bar')),
body: ListView.builder(
itemCount: 30,
itemBuilder: (context, index) => ListTile(
title: Text('${tabLabels[selectedIndex]} item ${index + 1}'),
),
),
tabBar: LiquidTabBar(
selectedIndex: selectedIndex,
onSelected: (index) => setState(() => selectedIndex = index),
items: const [
LiquidTabItem.icon(label: 'Home', icon: Icons.home_outlined),
LiquidTabItem.icon(label: 'Explore', icon: Icons.explore_outlined),
LiquidTabItem.icon(label: 'Profile', icon: Icons.person_outline),
],
),
),
);
}
}
Save the file and run flutter run.
selectedIndex tells the bar which tab is active. onSelected updates your
app state when the user chooses a tab. Replace the sample ListView with your
own content. LiquidTabBarScaffold lets the page draw behind the floating bar,
adds bottom space so the last list item stays visible, and handles scroll
folding. LiquidGlass.load() prepares the shader; the bar falls back to blur
when shader glass is unavailable.
For multiple destinations, keep one bar above an IndexedStack so the lens
can travel between pages.
Custom Icons #
Use standard icons or any Flutter widget, including SVGs, images, and custom
painters. LiquidTabItem.icon is the concise option for Material icons;
LiquidTabItem.custom accepts a widget and an optional activeIcon.
LiquidTabItem.custom(
label: 'Explore',
icon: SvgPicture.asset('assets/icons/explore.svg'),
activeIcon: SvgPicture.asset('assets/icons/explore_filled.svg'),
iconSize: 22,
useThemeColor: false, // preserve multicolor artwork
)
Custom widgets are tinted with the theme's selected and unselected colors by
default. Set useThemeColor: false to keep their original colors. The package
accepts ordinary widgets and does not bundle an SVG or image library.
Keyboard behavior #
The bar moves above the keyboard by default. To keep it at the bottom for
ordinary text fields, set liftAboveKeyboard: false and
resizeToAvoidBottomInset: false on the host Scaffold. Built-in search still
moves above the keyboard.
Styling & Optics #
LiquidTabBarTheme controls the selected and unselected colors, outer bar,
selected lens, and refraction. It follows the app's light or dark brightness by
default. Use LiquidTabBarTheme.adaptive(context) to also use the app's primary
color, or pin a palette with LiquidTabBarTheme.dark().
theme: LiquidTabBarTheme.adaptive(context).copyWith(
activeColor: const Color(0xFF007AFF),
barStyle: LiquidBarStyle.glossy(),
dropletRefraction: const DropletRefractionStyle.medium(),
),
Material tiers #
| Tier | Rendering |
|---|---|
auto (default) |
Uses shader glass when supported; otherwise falls back to blur. |
glass |
Fragment shaders sample and refract the backdrop (Impeller required). |
blur |
Cross-platform frosted glass. |
opaque |
Solid, high-contrast surface. |
Glossy and light/dark styles #
LiquidBarStyle.glossy() follows ambient brightness. Pass brightness: to pin
it. Glossy adds a clearer bevel while retaining the same glass family.
| Glossy Light | Glossy Dark |
|---|---|
![]() |
![]() |
theme: LiquidTabBarTheme(barStyle: LiquidBarStyle.glossy()),
Outer bar appearance is set by LiquidBarStyle and GlassStyle. Presets
include frosted, prismaticCaustics, clearCrystal, and deepRefraction.
The selected lens surface is configured independently through
LiquidDropletSurfaceStyle, including its gradient, border, and
LiquidDropletShadow.
Droplet refraction #
The moving lens bends the icons and labels behind it; displacement and color
separation fade at rest. DropletRefractionStyle presets are none(),
subtle(), medium() (default), and strong(). Set dispersion: 0 to disable
RGB separation while retaining refraction.
theme: const LiquidTabBarTheme(
dropletRefraction: DropletRefractionStyle.strong(),
),
Advanced controls are thickness (bevel width), refractiveIndex, baseHeight
(optical depth), dispersion, specularStrength, and refractionStrength.
When omitted, the theme selects calibrated Normal or Glossy values. Explicit
values are respected.
Actions & Placement #
| Together | Split |
|---|---|
![]() |
![]() |
Attach a standalone circular button (such as Create, Filter, or Search) alongside the navigation capsule:
separateAction: LiquidTabAction.icon(
icon: Icons.add_rounded,
tooltip: 'Create',
onTap: handleCreate,
),
separateActionPlacement: LiquidTabActionPlacement.together, // or .split
LiquidTabActionPlacement.together(default): Groups the action circle adjacent to the main capsule.LiquidTabActionPlacement.split: Pins the main capsule to the leading margin and the action button to the trailing margin.- Action styling: Customize the selected action background marker via
actionStyle: const LiquidTabActionStyle(selectedFill: ...).
Notification Badges #
LiquidTabItem includes integrated notification badges with four display modes:
// 1. Unread dot
LiquidTabItem.icon(
icon: Icons.mail_rounded,
label: 'Inbox',
badge: true,
),
// 2. Count pill (auto-formats 99+ above 99)
LiquidTabItem.icon(
icon: Icons.notifications_rounded,
label: 'Alerts',
badge: true,
badgeCount: 4,
),
- Text pill: Pass
badgeText: 'PRO'for custom string badges. - Custom widget: Supply
badgeWidgetfor custom indicator layouts. - Styling: Configure colors, borders, typography, and offsets via
LiquidBadgeStyle. - Optical interaction: Normal tab badges participate in droplet refraction when overlapped by the moving lens.
Expandable Search #
Add an edge-to-edge search field with a separate search action:
separateAction: LiquidTabAction.search(
hintText: 'Search notes…',
clearOnClose: true,
onChanged: filterResults,
onSubmitted: submitSearch,
customIcon: SvgPicture.asset('assets/icons/search.svg'), // optional
),
Search moves above the keyboard. Use the controller attached to the bar to call
controller.openSearch() or controller.closeSearch(). Android back closes
an active search before leaving the page. customIcon is also used in the
expanded field; custom icons support useThemeColor like tab icons.
Adaptive Folding #
Automatic Folding (Recommended) #
When using LiquidTabBarScaffold, vertical scrolling in primary body scrollables automatically folds the bar into a compact pill showing only the active tab:
LiquidTabBarScaffold(
body: ListView.builder(
itemCount: 50,
itemBuilder: (context, i) => ListTile(title: Text('Item $i')),
),
tabBar: LiquidTabBar(
shrinkOnScroll: true, // Set false to keep permanently expanded
foldedShape: LiquidFoldedShape.circle, // .circle or .oval
// ...
),
)
LiquidTabBarScaffold automatically observes primary vertical body scrolling; no NotificationListener or controller management is required for normal layouts. Scrolling back up, reaching the top of content, or tapping the folded capsule smoothly unfolds the bar.
Manual integration #
For custom Scaffold layouts, multiple independent vertical scroll sources, or complex nested scrolling, forward notifications explicitly:
final controller = LiquidTabBarController();
NotificationListener<ScrollNotification>(
onNotification: controller.handleScroll,
child: myScrollView,
)
LiquidTabBar(
controller: controller,
shrinkOnScroll: true,
// ...
)
Controller, layout & accessibility #
LiquidTabBarScaffold is the recommended layout: it enables extendBody,
reserves scroll space, and observes primary vertical scrolling for folding.
With a regular Scaffold, set extendBody: true and add
LiquidTabBar.reservedPadding(context) to scrollable content. For slivers, use
SliverLiquidScrollPadding().
For manual scroll handling, forward notifications to
LiquidTabBarController.handleScroll. The controller also exposes
minimize(), expand(), openSearch(), closeSearch(), and performance
governor status. Selection remains app-owned via selectedIndex and
onSelected.
The bar follows ambient RTL directionality and provides screen-reader
semantics. It respects MediaQuery.disableAnimationsOf(context) for reduced
motion. LiquidFoldedShape.circle is the default; use .oval for an oval
folded bar.
Advanced Governor Tuning #
When armed at startup via LiquidTabBarController.shared.armGovernor(), the governor monitors GPU raster timings during glass shader execution and automatically downgrades to backdrop blur if slow frames exceed default thresholds (rasterThresholdMs: 24, maxSlowFrames: 12).
For custom performance budgets, configure explicit thresholds on your controller:
final controller = LiquidTabBarController(
governorConfig: const LiquidGovernorConfig(
rasterThresholdMs: 20,
maxSlowFrames: 8,
),
);
controller.armGovernor();
Example Application #
The repository includes interactive demonstrations:
- 4-Style Comparison: Normal and Glossy bars in light and dark themes.
- Basic Navigation: Standard bottom bar with fluid spring droplet.
- Styling & Refraction: Custom materials, light/dark themes, and refraction presets.
- Action Buttons: Together and Split action placements.
- Search & Folding: Expandable search morphing, circle/oval folding, and live RTL layout.
- Custom Icons Demo: Standard
IconData, custom SVG widgets, activeIcon switching, theme tinting vs original multi-color artwork, custom Search glyphs, and Search glyph sizing. - Text Form Field: Keyboard behavior with the bar visible.
cd example
flutter run
2.0.0 migration #
Version 2.0.0 consolidates the public API around the droplet navigation bar and removes obsolete or duplicate options. See the migration guide for source changes.
Credits #
Version 2.0.0 consolidates the package around the droplet navigation bar. The bar, scaffold, refraction shader, search, actions, badges, style objects, and Android Impeller backdrop fix were created by Mohammed Hafiz (#5). Yousef Sobhy (#4) contributed the fold-and-unfold lens fixes in 1.0.1. The package originated in the Orderbase courier app.
License #
This package is licensed under the MIT License. See LICENSE for details.





