A glass bottom navigation bar, frosted or liquid, with an animated selection indicator, per-item active colours and two layouts.
![]() |
![]() |
![]() |
GlassyNavbarType.centereda floating pill |
GlassyNavbarType.bottomdocked to the edge |
with a centerDockedaction button |
![]() |
GlassStyle.liquidthe rim bends what is behind it |
Features
- Frosted glass — blurs whatever scrolls behind it, with a configurable sigma
- Liquid glass — the same blur, with the rim refracting the backdrop and catching light, falling back to frosted where it cannot run
- Animated background indicator that slides to the selected item
- Animated marker above the selected icon
- Per-item active colour, tinting both the indicator and the marker
- Separate icon for the selected state
- Two layouts: docked to the bottom edge, or a floating pill
- Labels on the selected item, the unselected items, both or neither
- Styleable labels, with a separate style for the selected one
- Self-tracking or fully controlled by the parent, for a
PageControlleror a router - Configurable blur, tint, border, corner radius, margin, padding, marker size and animation duration
Usage
The bar blurs what is painted behind it, so put it in a Scaffold with
extendBody: true:
Scaffold(
extendBody: true,
body: const MyPage(),
bottomNavigationBar: GlassyBottomNav(
onChange: (index) => setState(() => _index = index),
items: [
GlassyBottomNavItem(
icon: const Icon(Icons.home_outlined),
activeIcon: const Icon(Icons.home),
label: 'Home',
activeColor: Colors.green,
),
GlassyBottomNavItem(
icon: const Icon(Icons.favorite_border),
activeIcon: const Icon(Icons.favorite),
label: 'Favorite',
activeColor: Colors.red,
),
],
),
)
Without activeColor an item falls back to the theme's
colorScheme.secondary.
Layouts
navbarType picks the shape. GlassyNavbarType.centered (the default) floats
the bar as a pill, inset from the bottom and the sides:
GlassyBottomNav(
navbarType: GlassyNavbarType.centered,
items: items,
)
GlassyNavbarType.bottom docks it to the bottom edge, spanning the full width
with only the top corners rounded:
GlassyBottomNav(
navbarType: GlassyNavbarType.bottom,
items: items,
)
Either can be overridden with margin and borderRadius.
Controlled selection
By default the bar tracks the selection itself, starting at initialIndex.
Pass currentIndex when something else owns the current page — a
PageController, a router, or your own state. Taps then only report through
onChange, and the bar moves when you pass a new currentIndex:
GlassyBottomNav(
currentIndex: _index,
onChange: (index) {
setState(() => _index = index);
_pageController.animateToPage(
index,
duration: const Duration(milliseconds: 300),
curve: Curves.easeOut,
);
},
items: items,
)
Glass styles
glassStyle picks how the bar treats what is behind it.
GlassStyle.frosted is the default and the blur the bar has always drawn.
GlassStyle.liquid keeps that blur and bends the backdrop along the bar's
edge, with a lit rim:
GlassyBottomNav(
glassStyle: GlassStyle.liquid,
items: items,
)
That is the whole API. There is nothing to await, no capability to check and no second widget to swap in.
It needs Impeller. The refraction is a fragment shader, and
ui.ImageFilter.shader only exists on Impeller — so not on the web, and not
in a build still running Skia. Asking for liquid there is not an error and
does not throw: the bar resolves it to frosted and draws that instead. The
fallback is not something you arrange, it is what this package already
draws, so glassStyle: GlassStyle.liquid is safe to write unconditionally
in an app that also ships to the web.
To find out which one you will get before building a bar — to word a settings screen, say — ask the style itself:
if (GlassStyle.liquid.resolved == GlassStyle.liquid) {
// The shader will run here.
}
Two things worth knowing about how it looks:
backgroundBluris the control over the rim. What the rim bends is the blurred backdrop, so a low sigma leaves the bent content recognisable through the edge and a high one softens the rim into a plain bevel. The default of10sits at the sharp end.- The bar's own contents are painted on the glass, not through it. Icons, labels, the marker and the border stay crisp whatever the backdrop is doing.
The rim has no settings of its own. Band width, refraction distance and the highlight were tuned as a set and only read as glass in combination.
Styling the glass
GlassyBottomNav(
backgroundBlur: 20,
backgroundColor: Colors.black,
borderThickness: 0.5,
borderColor: Colors.white.withValues(alpha: 0.15),
showUnselectedLabel: false,
labelStyle: const TextStyle(fontSize: 11),
selectedLabelStyle: const TextStyle(fontWeight: FontWeight.bold),
markerSize: const Size(24, 3),
duration: const Duration(milliseconds: 250),
items: items,
)
GlassyBottomNav
| Parameter | Default | Description |
|---|---|---|
items |
required | The destinations, laid out with equal widths |
initialIndex |
0 |
The index selected on first build |
currentIndex |
null |
Set it to drive the selection from outside |
onChange |
null |
Called with the new index when a different item is tapped |
navbarType |
centered |
Floating pill, or docked to the bottom edge |
glassStyle |
frosted |
liquid refracts the rim where Impeller can run it |
showBackgroundIndicator |
true |
Tinted panel behind the selected item |
backgroundBlur |
10 |
Blur sigma applied to the backdrop |
backgroundColor |
null |
Tints the glass, at 10% opacity |
borderRadius |
type default | 25 for bottom, 50 for centered |
borderThickness |
null |
No border is drawn when null |
borderColor |
translucent white | Ignored unless borderThickness is set |
margin |
type default | Space around the bar |
padding |
EdgeInsets.zero |
Space between the bar's edges and its items |
showSelectedLabel |
true |
Label under the selected icon |
showUnselectedLabel |
true |
Labels under the other icons |
labelStyle |
white 12px | Merged over the default label style |
selectedLabelStyle |
null |
Merged over labelStyle when selected |
markerSize |
Size(20, 3) |
Marker above the selected icon |
duration |
150ms | Indicator and marker animation |
GlassyBottomNavItem
| Parameter | Default | Description |
|---|---|---|
icon |
required | Shown while the item is not selected |
activeIcon |
icon |
Shown while the item is selected |
label |
required | Text under the icon, and the item's tooltip |
activeColor |
colorScheme.secondary |
Tints the marker and the indicator |
Example
See example/ for a four-destination app that drives the bar from
a PageController. Its header has two switches: one flips between the layouts,
the other docks a FloatingActionButtonLocation.centerDocked button over the
bar, which works with either layout.
Libraries
- A frosted-glass bottom navigation bar with an animated selection indicator, per-item active colours and two layouts.



