bubble_label 5.1.0
bubble_label: ^5.1.0 copied to clipboard
A small Flutter package that shows a floating bubble label anchored to a child widget, and optional background overlay + simple show/dismiss animations.
bubble_label #
A small Flutter package that shows a floating bubble label aligned to a child widget.
This provides a lightweight API to display a bubble-style label anchored to a widget, including optional background overlay and simple show/dismiss animations.
Features #
- Simplified API (v5.0.0) — No GlobalKey boilerplate needed! Just pass
contextand the bubble anchors to that widget automatically. - Transform-aware positioning (v5.1.0) — Bubbles correctly position themselves even when ancestor widgets contain transforms like
Transform.scale,ForcePhoneSizeOnWeb, orFittedBox. - Bubble label content that can be positioned and sized.
- Background overlay with configurable opacity.
- Simple show/dismiss animations.
- Easy to use static API:
BubbleLabel.show(...)andBubbleLabel.dismiss(). - Works reliably in complex widget trees with robust overlay detection.
Example app #
See the example/ directory which demonstrates basic usage of the package.

Installation #
Add the package as a dependency in your app's pubspec.yaml:
dependencies:
bubble_label: ^5.1.0
When using this package from outside the repository (published), replace the path dependency with a hosted version.
Basic usage #
No wrapper widget is required! The package uses Flutter's native Overlay system, which is automatically provided by MaterialApp, CupertinoApp, or any widget tree that includes an Overlay.
Simplest usage (v5.0.0+):
import 'package:flutter/material.dart';
import 'package:bubble_label/bubble_label.dart';
// In your widget's build method:
Builder(
builder: (context) {
return ElevatedButton(
onPressed: () {
BubbleLabel.show(
context: context, // Uses this button as anchor!
bubbleContent: BubbleLabelContent(
child: const Text('Hello bubble!'),
bubbleColor: Colors.blue,
),
);
},
child: const Text('Show Bubble'),
);
},
);
That's it! No GlobalKey needed. The bubble will appear above the button that was tapped.
With explicit anchor (when needed):
final myKey = GlobalKey();
ElevatedButton(
key: myKey,
onPressed: () {
BubbleLabel.show(
context: context,
anchorKey: myKey, // Anchor to a specific widget
bubbleContent: BubbleLabelContent(
child: const Text('Anchored bubble'),
),
);
},
child: const Text('Show'),
);
For a full runnable example, see the example/ directory.
Example app features #
- Toggle
Allow bubble pointer events— controls whether the bubble receives pointer events (setsshouldIgnorePointeronBubbleLabelContent). When this is off the bubble ignores taps; turning it on allows the bubble to receive pointer events. - Toggle
Animate(show/dismiss animations — uses theanimateparameter when callingBubbleLabel.showorBubbleLabel.dismiss). - Toggle
Use overlay— when off, only the bubble is shown without any background overlay. - Buttons:
- Show — demonstrates a standard bubble with overlay (if enabled).
- Tap: show without overlay — demonstrates a bubble with no overlay (explicitly sets overlay to 0.0).
- Dismiss — dismisses the bubble immediately (
animate: false). - Dismiss (animated) — dismisses the bubble with animation (
animate: true). - Long-press area — long-pressing this area will call
BubbleLabel.showwithshouldActivateOnLongPressOnAllPlatformsset; you can toggleUse overlayandAnimateto see the effect.
API #
Key public pieces of the API:
BubbleLabel.show(...)— show the bubble overlay with provided content and parameters.contextis required and serves two purposes: (1) finding the Overlay widget, and (2) as the default anchor for positioning the bubble. If you want to anchor to a different widget, pass ananchorKey. Alternatively, useBubbleLabelContent.positionOverridefor explicit screen coordinates.BubbleLabel.dismiss()— dismiss the bubble.BubbleLabel.isActive— boolean property indicating whether a bubble is currently active.BubbleLabel.controller— access to the injected controller instance for programmatic inspection or updates (advanced use).BubbleLabel.updateContent(...)— update the active bubble's content dynamically without dismissing it (new in v4.0.0).BubbleLabel.tapRegionGroupId— the group ID used by the bubble'sTapRegion, allowing external widgets to be considered "inside" the bubble (new in v4.0.0).
BubbleLabelContent fields include (defaults shown where applicable):
child— the content widget of the bubble.bubbleColor— bubble background color.- The bubble now adapts to the
childsize; explicitlabelWidth/labelHeightare removed. _childWidgetRenderBox— internal storage for the anchor widget'sRenderBoxwhen ananchorKeyis supplied toBubbleLabel.show. The bubble size/position still honorspositionOverridewhen you need to anchor to specific coordinates instead.positionOverride— optional explicitOffsetto anchor the bubble directly.backgroundOverlayLayerOpacity— opacity for the background overlay.shouldIgnorePointer— whentrue, pointer events pass through the bubble content (new in v4.0.0).onTapInside— callback triggered when a tap is detected inside the bubble (new in v4.0.0).onTapOutside— callback triggered when a tap is detected outside the bubble (new in v4.0.0).
Optional parameters you might find handy:
bubbleColor— use to customize bubble color.backgroundOverlayLayerOpacity— use to dim the underlying UI; set to0.0to disable.animateflag inshow()anddismiss()— passfalseduring testing or if you want immediate effect.
Dismissing the bubble
// programmatic dismissal, immediate
BubbleLabel.dismiss(animate: false);
// programmatic dismissal, with animation
BubbleLabel.dismiss(animate: true);
// The example app has dedicated buttons showing these two dismissal modes.
floatingVerticalPadding— adjust the vertical offset between the child widget and bubble.shouldActivateOnLongPressOnAllPlatforms— a hint to indicate that the bubble was triggered by long-press; you can set this to true when using long-press activation.
To run the example application locally:
cd example
flutter run
Testing & debugging tip: The package exposes a small controller that can be inspected from tests to assert active bubble state or properties.
Important API changes in v2.0.0 #
- Removed
labelWidth/labelHeight: the bubble adapts to thechildsize automatically. - Removed
childWidgetPosition/childWidgetSize— instead rely on the anchor automatically resolved from theanchorKeyyou pass toBubbleLabel.show, or fall back topositionOverridewhen specifying explicit screen coordinates. BubbleLabelContentnow includes anidanddismissOnBackgroundTapto enable automatic background tap dismissals.
Migrating from v4.x to v5.0.0 #
Before (v4.x):
final myKey = GlobalKey();
ElevatedButton(
key: myKey,
onPressed: () {
BubbleLabel.show(
anchorKey: myKey,
bubbleContent: BubbleLabelContent(
child: Text('Hello'),
),
);
},
child: Text('Show'),
);
After (v5.0.0) - Simplified:
Builder(
builder: (context) {
return ElevatedButton(
onPressed: () {
BubbleLabel.show(
context: context, // Uses this button as anchor!
bubbleContent: BubbleLabelContent(
child: Text('Hello'),
),
);
},
child: Text('Show'),
);
},
);
After (v5.0.0) - With explicit anchorKey (optional):
BubbleLabel.show(
context: context, // Required for overlay lookup
anchorKey: myKey, // Optional: anchor to a different widget
bubbleContent: BubbleLabelContent(
child: Text('Hello'),
),
);
Migrating from v3.x to v4.0.0 #
Before (v3.x):
BubbleLabelController(
shouldIgnorePointer: false,
child: MaterialApp(...),
);
After (v4.0.0):
MaterialApp(...); // No wrapper needed!
BubbleLabel.show(
bubbleContent: BubbleLabelContent(
child: Text('Hello'),
shouldIgnorePointer: false, // Now set per-bubble
),
anchorKey: myKey,
);
Advanced usage
// Simplest usage: context widget becomes the anchor (no GlobalKey needed!)
Builder(
builder: (context) {
return ElevatedButton(
onPressed: () {
BubbleLabel.show(
context: context, // This button is the anchor
bubbleContent: BubbleLabelContent(
child: const Text('No overlay'),
bubbleColor: Colors.green,
backgroundOverlayLayerOpacity: 0.0,
verticalPadding: 10, // 10 px above the anchor
),
animate: true,
);
},
child: const Text('Show without overlay'),
);
},
);
// With explicit anchorKey (when you need to anchor to a different widget)
final showButtonKey = GlobalKey();
ElevatedButton(
key: showButtonKey,
onPressed: () {
BubbleLabel.show(
context: context,
anchorKey: showButtonKey, // Anchor to this specific widget
bubbleContent: BubbleLabelContent(
child: const Text('Anchored to key'),
),
);
},
child: const Text('Show'),
);
// Show a bubble with an explicit screen offset anchor (use `positionOverride`)
BubbleLabel.show(
context: context,
bubbleContent: BubbleLabelContent(
child: const Text('Position override bubble'),
positionOverride: Offset(200, 150), // anchor at (200,150) screen coords
),
);
When using the simplified context-as-anchor approach, wrap your widget in a Builder to get a context that refers to that specific widget. If you need dynamic position tracking (e.g., the anchor widget moves), use a GlobalKey instead.
Reactive Updates (v4.0.0+) #
Use BubbleLabel.updateContent() to dynamically update the bubble while it's displayed:
// Update a property while the bubble is open
BubbleLabel.updateContent(shouldIgnorePointer: true);
TapRegion Group Integration (v4.0.0+) #
If you have external widgets (like toggles or controls) that should be considered "inside" the bubble for tap detection purposes, wrap them with a TapRegion using the bubble's group ID:
TapRegion(
groupId: BubbleLabel.tapRegionGroupId,
child: YourControlWidget(),
)
Taps on these wrapped widgets won't trigger onTapOutside or dismiss the bubble via dismissOnBackgroundTap.
Tap Event Callbacks (v4.0.0+) #
Use onTapInside and onTapOutside callbacks for custom tap handling:
BubbleLabelContent(
child: const Text('Interactive bubble'),
onTapInside: (details) {
// Handle tap inside bubble
},
onTapOutside: (details) {
// Handle tap outside bubble
},
)
Changelog #
5.1.0 (2026-01-06) - Transform-Aware Positioning #
Bug Fixes:
- Fixed bubble positioning with ancestor transforms — Bubbles now correctly position themselves when the widget tree contains transforms such as
Transform.scale,ForcePhoneSizeOnWeb(fromflutter_web_frame), orFittedBox. Both the anchor position and visual size are computed relative to the Overlay's coordinate system. - Fixed bubble replacement during transforms — When showing a new bubble while one is already active, the Overlay's RenderBox reference is now correctly preserved.
- Fixed crash when toggling transforms with active bubble — Added safety checks for
RenderBox.attachedto prevent crashes when the widget tree is rebuilt while a bubble is active.
Example App:
- Added
Transform.scaleandForcePhoneSizeOnWebtoggles to demonstrate transform-aware positioning.
5.0.0 (2026-01-06) - Simplified API with Required Context #
BREAKING CHANGES:
contextis now a required parameter inBubbleLabel.show()— This ensures reliable overlay detection in complex widget trees.anchorKeyis now optional — Thecontextwidget is used as the anchor by default. PassanchorKeyonly when you need to anchor to a different widget.
New Features:
- Simplified API — No more GlobalKey boilerplate for simple use cases! Just pass
contextand the bubble anchors to that widget. - Context-as-anchor — When no
anchorKeyorpositionOverrideis provided, the widget fromcontextbecomes the anchor.
Improvements:
- Robust overlay lookup strategy — Now tries
rootOverlay: truefirst, then falls back torootOverlay: falsefor maximum compatibility. - Better error messages — Clearer guidance when overlay detection fails, with specific solutions.
Migration from v4.x:
// Before (v4.x)
BubbleLabel.show(
anchorKey: myKey,
bubbleContent: BubbleLabelContent(...),
);
// After (v5.0.0)
BubbleLabel.show(
context: context, // Now required!
anchorKey: myKey,
bubbleContent: BubbleLabelContent(...),
);
4.0.0 (2025-12-12) - Overlay-based Implementation & Enhanced Interactivity #
BREAKING CHANGES:
- Removed
BubbleLabelControllerwidget requirement — The package now uses Flutter's nativeOverlaysystem, eliminating the need to wrap your app with a custom controller widget. - Migrated from Stack-based to Overlay-based rendering — Uses native
OverlayEntryfor better performance. - Moved
shouldIgnorePointerfromBubbleLabelControllertoBubbleLabelContent— Now set per-bubble instead of globally.
New Features:
shouldIgnorePointerproperty onBubbleLabelContent— control whether pointer events pass through the bubble content.onTapInside/onTapOutsidecallbacks onBubbleLabelContent— respond to tap events inside or outside the bubble.BubbleLabel.updateContent()— dynamically update the active bubble's content without dismissing it.BubbleLabel.tapRegionGroupId— exposed group ID allowing external widgets to be considered "inside" the bubble for tap detection.
Improvements:
- Replaced
IgnorePointerwithAbsorbPointerfor properTapRegionhit testing. - Dismiss animation now uses a cancellable
Timerto prevent race conditions. - Fixed BuildContext async gap lint warning in dismiss flow.
- Removed all
debugPrintstatements from the library. - Hybrid validation system for better overlay detection feedback.
3.0.0 (2025-12-05) #
- Boilerplate reduction:
BubbleLabel.shownow automatically resolves the anchorRenderBoxwhen passing ananchorKey. - Stricter input validation: must provide exactly one anchor source (
anchorKeyorpositionOverride).
2.0.0 (2025-11-30) #
- Major API update: rendering and anchoring simplified via
childWidgetRenderBoxandpositionOverride; automatic bubble sizing tochild. - Added
idtoBubbleLabelContentanddismissOnBackgroundTapoption to easily dismiss overlay by tapping. - Updated example app and tests to reflect the new API and control toggles.
1.0.1 (2025-11-25) #
- Added public documentation (dartdoc) for public API types and members.
- Enabled
public_member_api_docslint inanalysis_options.yamlto enforce documentation for public members. - Cleaned up README formatting and clarified example usage.
- Verified tests for overlay opacity, pointer behavior, and animations.
License #
This project is licensed under the MIT License — see the LICENSE file for details.
