snap_nav_bar
A frosted, gesture-driven bottom navigation bar with loose swipe-to-preview paging, snap-on-release physics, tap-to-centre navigation and haptic feedback.
What it does
- Swipe the pages — the content
PageViewuses a soft custom spring so a flick glides and settles instead of snapping hard. - Swipe the bar — dragging the tab strip previews the move 1:1 under your finger while the page stays frozen; the page only commits (sliding smoothly) when you let go. A fast fling nudges one extra tab.
- Tap any tab — near or far — and it slides straight to that page and centres it.
- Haptics — a selection tick as each tab crosses centre and a soft impact when the page locks in. Both are swappable, or off entirely.
- Frosted squircle surface —
BackdropFilterblur behind an iOS-style squircle strip, with a centre indicator over the active tab.
No AnimationController, Ticker, or flutter_animate — the strip easing is
done with TweenAnimationBuilder and PageController animations only. Icons
are arbitrary widgets you supply; the package has no SVG or asset dependency.
Install
dependencies:
snap_nav_bar: ^0.1.0
Usage
The quickest path is SnapNavShell, which owns the content PageView and the
bar and wires them together:
import 'package:flutter/material.dart';
import 'package:snap_nav_bar/snap_nav_bar.dart';
class Demo extends StatelessWidget {
const Demo({super.key});
@override
Widget build(BuildContext context) {
return SnapNavShell(
items: const [
SnapNavItem(
icon: Icon(Icons.home_outlined),
activeIcon: Icon(Icons.home_rounded),
label: 'Home',
page: ColoredBox(color: Color(0xFF3B82F6)),
),
SnapNavItem(
icon: Icon(Icons.search_rounded),
label: 'Search',
page: ColoredBox(color: Color(0xFF10B981)),
),
SnapNavItem(
icon: Icon(Icons.person_rounded),
label: 'Profile',
page: ColoredBox(color: Color(0xFFEF4444)),
),
],
onTabChanged: (i) => debugPrint('tab $i'),
);
}
}
Want SVG icons? Pass an SvgPicture (or anything) as the icon — the package
never touches SVG itself. Driving your own PageView? Use SnapNavBar
directly with a PageController (viewportFraction: 1.0).
The bar is frosted, so it blurs whatever is painted behind it — keep it
overlaid on your content (which SnapNavShell does for you).
See example/ for a runnable two-screen demo: one all-defaults,
one heavily customised (6 tabs, solid floating pill, no blur, custom
indicator, swapped haptics).
Parameters
SnapNavShell exposes everything below. SnapNavBar exposes the same appearance,
motion, indicator and feedback parameters (it takes a PageController and
items instead of owning them, and viewportFraction / initialIndex /
height live on the shell).
Content
| Parameter | Type | Default | Description |
|---|---|---|---|
items |
List<SnapNavItem> |
required | Tabs; each carries icon, optional activeIcon, optional label, and its page. Min 1, any length. |
initialIndex |
int |
0 |
Tab shown first. |
onTabChanged |
ValueChanged<int>? |
null |
Fires when the selected page changes. |
Layout & motion
| Parameter | Type | Default | Description |
|---|---|---|---|
viewportFraction |
double |
1.0 |
How much of each content page fills the viewport; below 1.0 the neighbouring pages peek in. |
height |
double |
76.0 |
Bar height above the bottom safe-area inset. |
tabSpacing |
double |
52.0 |
Logical px of finger travel per tab while dragging the strip. |
horizontalMargin |
double |
0.0 |
Space left/right of the bar. |
bottomMargin |
double |
0.0 |
Space below the bar (lifts it into a floating pill). |
tapToCenterDuration |
Duration |
300 ms |
Slide duration for a tab tap / drag release. |
tapToCenterCurve |
Curve |
Curves.easeOutCubic |
Slide curve for the same. |
activeOpacity |
double |
1.0 |
Opacity of the centred tab. |
inactiveOpacity |
double |
0.5 |
Opacity of a fully off-centre tab (tabs crossfade between the two). |
activeScale |
double |
1.0 |
Scale of the centred tab. |
inactiveScale |
double |
0.92 |
Minimum scale an off-centre tab shrinks to. |
Shape & surface
| Parameter | Type | Default | Description |
|---|---|---|---|
backgroundColor |
Color? |
Color(0x40808080) |
Bar fill (ignored if a gradient is set). |
backgroundGradient |
Gradient? |
null |
Bar fill gradient; overrides backgroundColor. |
cornerRadius |
double |
32.0 |
Corner radius. |
cornerSmoothing |
double |
1.0 |
0.0 plain rounded rect → 1.0 full squircle. |
blurSigma |
double? |
24.0 |
Backdrop blur; null disables the frost entirely. |
border |
BoxBorder? |
null |
Uniform border on the squircle edge. |
boxShadow |
List<BoxShadow>? |
null |
Drop shadow, painted outside the clip. |
Indicator
| Parameter | Type | Default | Description |
|---|---|---|---|
indicator |
Widget? |
null |
Custom centre indicator; null draws the built-in arrow. |
showIndicator |
bool |
true |
Whether the indicator shows at all. |
indicatorColor |
Color |
Color(0xFFE53935) |
Colour of the built-in arrow. |
indicatorSize |
Size |
Size(12, 8) |
Indicator slot size. |
Feedback
| Parameter | Type | Default | Description |
|---|---|---|---|
hapticsEnabled |
bool |
true |
Master haptics switch. |
hapticFeedback |
void Function(SnapNavHaptic)? |
null |
Swap the effect fired at each trigger point. null uses selectionClick on cross and lightImpact on settle. |
License
MIT — see LICENSE.
Libraries
- A frosted, gesture-driven bottom navigation bar with loose swipe-to-preview paging, snap-on-release physics, tap-to-centre and haptic feedback.