flutter_floating_bottom_bar 2.1.0
flutter_floating_bottom_bar: ^2.1.0 copied to clipboard
An adaptable Flutter floating bottom bar for navigation, search, and custom widgets, with scroll-aware visibility and accessible interactions.
Floating Bottom Bar #
A Flutter package that floats any widget above your content and reacts to scrolling. Use it as a tab bar, bottom navigation bar, search bar, command surface, or any custom child.
![]() |
![]() |
![]() |
|---|---|---|
| Issues dock | AI prompt dock | Basic TabBar |
![]() |
![]() |
![]() |
| Minimal API | Nested scroll | Badged nav |
![]() |
||
| Custom transition |
Why this package #
- Host any widget as the floating bar:
TabBar,BottomBarItems, a search composer, or a customRow. - Scroll detection is notification-based. Most setups need no
ScrollControllerplumbing. BottomBarLayout.adaptive(maxWidth: ...)fills the host width while keeping a hard cap for tablets, desktop, and wide layouts.BottomBarBodyPaddingreserves the bar's full measured footprint, including configured offset and bottom safe-area, so body content stays clear even while the bar is hidden.BottomBarMotion()defaults to Motor-backed Cupertino spring motion with velocity-preserving redirects when scroll direction changes mid-animation.- Custom transitions are supported, but they must stay paint-only and preserve the child's layout footprint.
- The hidden action is accessibility-safe by default: it is non-interactive and
excluded from semantics while the bar is visible, keeps at least a 48x48 hit
target when active, uses direction-aware icon/tooltip text, and defaults its
glyph color to
ColorScheme.onPrimary. - Reduced-motion environments snap directly to the final shown/hidden state.
BottomBarControllersupports imperative show/hide plusscrollToStart()/scrollToEnd(), including correctNestedScrollViewboundary targeting.Motionfrom themotorpackage is re-exported intentionally forBottomBarMotion.motor(...).
Install #
Requires Dart >=3.5.0 and Flutter >=3.22.0.
See the 2.1.0 release notes for the complete list of new layout, scrolling, accessibility, and reliability improvements.
flutter pub add flutter_floating_bottom_bar
import 'package:flutter_floating_bottom_bar/flutter_floating_bottom_bar.dart';
Claude Code skill #
If you use Claude Code, this repo ships a skill for integration, migration, and debugging help around this package.
Install:
/install-skill https://raw.githubusercontent.com/codenameakshay/flutter-floating-bottom-bar/main/.claude/skills/flutter-floating-bottom-bar.skill
Basic usage #
BottomBar(
layout: const BottomBarLayout.adaptive(maxWidth: 420),
body: BottomBarBodyPadding(
padding: const EdgeInsets.only(top: 24),
child: ListView.builder(
padding: const EdgeInsets.symmetric(horizontal: 16),
itemCount: 50,
itemBuilder: (_, index) => ListTile(title: Text('Item $index')),
),
),
child: const Padding(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 14),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Icon(Icons.home_rounded),
Text('Floating bar'),
Icon(Icons.search_rounded),
],
),
),
)
BottomBar needs two required arguments:
body: the subtree that emitsScrollNotifications.child: the floating widget shown above that body.
The bar is stacked above the body, so use BottomBarBodyPadding when your body
should reserve enough bottom space to stay clear of the bar.
Common recipes #
Adaptive width + stable body padding #
Use BottomBarLayout.adaptive(maxWidth: ...) to span narrow screens while
stopping at a defined maximum on wide screens. Pair it with
BottomBarBodyPadding when the body should reserve the full footprint.
BottomBar(
layout: const BottomBarLayout.adaptive(maxWidth: 440),
body: const BottomBarBodyPadding(
child: CustomScrollView(
slivers: [
SliverAppBar(title: Text('Inbox')),
SliverList.list(
children: [
ListTile(title: Text('Message 1')),
ListTile(title: Text('Message 2')),
],
),
],
),
),
child: const SizedBox(
height: 56,
child: Center(child: Text('Compose')),
),
)
BottomBarScope.barHeight and BottomBarBodyPadding use the bar's measured
layout footprint, not its animated transform. The reported value includes the
bar height plus configured layout.offset and bottom safe-area when
respectSafeArea is enabled.
Nested scroll views #
BottomBarController.scrollToStart() always targets the minimum extent and
scrollToEnd() always targets the maximum extent. In a NestedScrollView, the
controller automatically chooses the correct coordinated boundary controller:
scrollToStart()drives the outer controller so pinned/expanded header slivers return to their true top state.scrollToEnd()drives the inner controller so the body scrolls to the end and the header collapses naturally.
If your body emits unrelated notifications, use
BottomBarScrollBehavior.predicate to filter them.
BottomBar(
controller: controller,
scrollBehavior: BottomBarScrollBehavior(
predicate: (notification) => notification.depth == 0,
showAtStart: true,
showOnScrollEnd: true,
),
body: NestedScrollView(
headerSliverBuilder: (_, __) => const [
SliverAppBar(
pinned: true,
expandedHeight: 180,
flexibleSpace: FlexibleSpaceBar(title: Text('Nested scroll')),
),
],
body: ListView.builder(
itemCount: 100,
itemBuilder: (_, index) => ListTile(title: Text('Row $index')),
),
),
child: const SizedBox(
height: 56,
child: Center(child: Text('Scroll-aware')),
),
)
Custom transitions #
Custom transition builders must preserve the child's layout footprint. Use
paint-only wrappers such as Opacity, Transform.translate, or
Transform.scale.
Do not use layout-changing widgets such as SizeTransition or Align with a
heightFactor; those break the stable footprint contract used by
BottomBarScope.barHeight and BottomBarBodyPadding.
BottomBar(
motion: BottomBarMotion(
transitionBuilder: (context, animation, child) {
final value = animation.value.clamp(0.0, 1.0);
return Opacity(
opacity: value,
child: Transform.scale(
scale: 0.92 + (0.08 * value),
child: child,
),
);
},
),
body: ListView.builder(
itemCount: 100,
itemBuilder: (_, index) => ListTile(title: Text('Row $index')),
),
child: const SizedBox(
height: 56,
child: Center(child: Text('Paint-only transition')),
),
)
Spring progress can overshoot. Clamp animation.value before using it for
opacity or scale.
BottomBarItems and BottomBarItem #
BottomBarItems is a row helper. BottomBarItem is an opinionated item widget
with built-in accessibility and RTL-aware badge placement.
- The badge uses
PositionedDirectional, so it follows the top-end corner in both LTR and RTL layouts. - The item exposes button semantics, selection state, and enabled state.
- The tappable surface keeps at least a 48x48 target.
- Accessible naming is deterministic:
semanticLabelwins, otherwisetooltip, otherwise descendant semantics. - When
semanticLabelortooltipprovides the explicit accessible name, descendant semantics are excluded to avoid duplicate announcements.
Migration from v1.x #
| v1.x | v2.x | Migration |
|---|---|---|
body: (context, controller) => Widget |
body: Widget |
Drop the builder. The bar listens to ScrollNotifications from descendant scrollables. |
barColor |
removed | Use BottomBarThemeData.barDecoration or theme:. |
width, offset, borderRadius, barAlignment, fit, clip, respectSafeArea |
BottomBarLayout |
Move these into layout: BottomBarLayout(...). barAlignment became alignment. |
duration, curve, start, end |
BottomBarMotion |
BottomBarMotion() now defaults to Cupertino spring motion. start/end became slideStart/slideEnd. |
hideOnScroll, reverse, scrollOpposite, scrollDeltaThreshold |
BottomBarScrollBehavior |
Move these into scrollBehavior: BottomBarScrollBehavior(...). scrollDeltaThreshold became deltaThreshold. |
iconWidth, iconHeight, iconDecoration, barDecoration |
BottomBarThemeData |
Move these to the theme or the theme: override. |
BottomBarScrollControllerProvider |
BottomBarScope |
The old controller provider was replaced by BottomBarScope, which exposes barHeight and isVisible. |
No backward-compatibility shim ships with v2.
API quick reference #
BottomBar #
| Parameter | Type | Default | Notes |
|---|---|---|---|
child |
Widget |
required | Floating content shown above the body. |
body |
Widget |
required | Descendant scrollables here drive hide/show via notifications. |
controller |
BottomBarController? |
null |
Imperative show/hide/scroll API. |
layout |
BottomBarLayout? |
null |
Falls back to theme layout, then const BottomBarLayout(). |
motion |
BottomBarMotion? |
null |
Falls back to theme motion, then const BottomBarMotion(). |
scrollBehavior |
BottomBarScrollBehavior? |
null |
Falls back to theme scroll behavior, then const BottomBarScrollBehavior(). |
theme |
BottomBarThemeData? |
null |
Per-instance theme overrides. |
icon |
BackToTopIconBuilder? |
null |
Custom hidden action visual. |
showIcon |
bool |
true |
Enables or removes the built-in hidden action. |
iconSemanticLabel |
String? |
null |
Defaults to the direction-aware tooltip text. |
iconTooltip |
String? |
null |
Defaults to Scroll to top or Scroll to bottom. |
The hidden action is only interactive while the bar is hidden. While the bar is visible it is ignored for hit testing and removed from the semantics tree.
BottomBarLayout #
| Field | Type | Default | Notes |
|---|---|---|---|
width |
double |
300 |
Requested bar width before viewport/max-width clamping. |
maxWidth |
double? |
null |
Optional explicit width cap after host constraints. |
offset |
double |
10 |
Outer padding applied around the bar or hidden action. |
borderRadius |
BorderRadius |
BorderRadius.zero |
Used by the default rectangular bar decoration. |
iconOffset |
Offset |
Offset.zero |
Extra translation applied only to the hidden action. |
alignment |
Alignment |
Alignment.bottomCenter |
Shared alignment for the bar and hidden action. |
fit |
StackFit |
StackFit.loose |
Host stack fit. |
clip |
Clip |
Clip.hardEdge |
Host stack clip behavior. |
respectSafeArea |
bool |
true |
Wraps the bar and hidden action in SafeArea. |
Use BottomBarLayout.adaptive(maxWidth: ...) to fill available width up to a
hard cap. When deriving layouts, copyWith(clearMaxWidth: true) explicitly
removes an existing cap.
BottomBarMotion #
| Field | Type | Default | Notes |
|---|---|---|---|
mode |
BottomBarMotionMode |
cupertino |
BottomBarMotion() defaults to Cupertino spring mode. |
duration |
Duration |
500ms in default Cupertino mode |
Used for curved motion and scroll-to-boundary animation timing. |
curve |
Curve |
Curves.easeOutCubic |
Used by curved motion and controller boundary scrolling. |
cupertinoPreset |
BottomBarCupertinoMotion |
snappy |
Default Cupertino preset. |
transition |
BottomBarTransition |
slide |
Built-in transition when transitionBuilder is null. |
transitionBuilder |
Widget Function(BuildContext, Animation<double>, Widget)? |
null |
Overrides the enum. Must preserve layout footprint and stay paint-only. |
slideStart |
Offset |
Offset(0, 2) |
Hidden offset for slide and slideAndFade. |
slideEnd |
Offset |
Offset.zero |
Visible offset for slide and slideAndFade. |
Reduced-motion environments snap to the target shown/hidden state instead of animating.
BottomBarScrollBehavior #
| Field | Type | Default | Notes |
|---|---|---|---|
hideOnScroll |
bool |
true |
Disables scroll-driven hiding when false. |
reverse |
bool |
false |
Inverts the scroll direction that hides vs shows the bar. |
scrollOpposite |
bool |
false |
Changes only the built-in hidden action direction, tooltip, and glyph. It does not change scrollToStart() or scrollToEnd(). |
deltaThreshold |
double |
8 |
Minimum absolute delta required before visibility can flip. |
showAtStart |
bool |
false |
Forces the bar visible when a scrollable reaches its minimum extent. |
showOnScrollEnd |
bool |
false |
Forces the bar visible when scrolling settles. |
predicate |
bool Function(ScrollNotification)? |
null |
Skip notifications entirely when it returns false. |
BottomBarController #
| Member | Notes |
|---|---|
isVisible |
Tracks the currently attached bar's visibility. |
isAttached |
Whether the controller is attached to a live bar. |
show() / hide() / toggle() |
Imperative visibility controls. |
scrollToStart() |
Always scrolls the last active scrollable to its minimum extent. |
scrollToEnd() |
Always scrolls the last active scrollable to its maximum extent. |
A controller can own only one live bar at a time. Double-attach fails in both debug and release, and visibility updates are accepted only from the owning bar binding.
BottomBarScope #
BottomBarScope exposes two listenables inside BottomBar.body:
barHeight: the live measured bar footprint, including offset and bottom safe-area when enabled.isVisible: the current shown/hidden target state.
Use BottomBarBodyPadding when you want the common "reserve the bottom
footprint for me" behavior without wiring your own ValueListenableBuilder.
Examples and demos #
- API recipes: EXAMPLES.md
- Runnable demo app:
example/ - Demo entry point:
example/lib/main.dart
Contributing #
See CONTRIBUTING.md for setup, quality checks, and release dry-run steps.
Bugs or requests #
Open an issue at https://github.com/codenameakshay/flutter-floating-bottom-bar/issues.






