Overlay and OverlayEntry: Building Floating UI in Flutter
Build tooltips, toasts, and anchored floating menus with Overlay and OverlayEntry — lifecycle, stacking order, and the leak gotchas that bite in production.
Published on • November 2, 2026
AI Assistant

Tooltips, toasts, and anchored menus need to float above the current screen without pushing a route — that is exactly what Overlay and OverlayEntry provide (docs.flutter.dev). You own the insert/remove lifecycle, which makes them both flexible and easy to leak.
Overlay.of(context) and the entry lifecycle
Overlay.of(context) returns the nearest Overlay (usually the root one). Create an entry, insert it, and always remove it in dispose.
OverlayEntry? _entry;
void _show(BuildContext context) {
_entry?.remove();
_entry = OverlayEntry(builder: (_) => const _FloatingMenu());
Overlay.of(context, rootOverlay: true).insert(_entry!);
}
@override
void dispose() {
_entry?.remove();
_entry = null;
super.dispose();
}
Never leave an entry behind: after a route change a stale entry keeps rebuilding with a disposed State, leaking the overlay and eventually crashing on a dead context.
Stacking order and markNeedsBuild
Entries render in insertion order inside a Stack — later inserts paint above earlier ones. Because an entry builds outside your widget’s build, changing what it shows requires markNeedsBuild() on the entry itself.
void _refreshTip(String message) {
_message = message;
_entry?.markNeedsBuild();
}
End-to-end: an anchored tooltip
Pair CompositedTransformTarget on the anchor with a CompositedTransformFollower inside the entry — both sharing one LayerLink — and the bubble tracks the widget through scrolls and layout changes. _TipBubble below is your own small tooltip widget.
final _link = LayerLink();
OverlayEntry? _tip;
void _toggleTip(BuildContext context) {
if (_tip != null) { _tip!.remove(); _tip = null; return; }
_tip = OverlayEntry(
builder: (_) => CompositedTransformFollower(
link: _link,
targetAnchor: Alignment.topCenter,
followerAnchor: Alignment.bottomCenter,
child: const _TipBubble(message: 'Saved just now'),
),
);
Overlay.of(context).insert(_tip!);
}
// The anchor shares the same LayerLink:
CompositedTransformTarget(link: _link, child: const Icon(Icons.info));
When to use Overlay vs Dialog vs PopupMenuButton
Overlayfor floating UI tied to a widget’s lifetime: custom tooltips, toasts, anchored menus.showDialogwhen you need a modal barrier, route semantics, and free back-button handling.PopupMenuButtonfor the standard anchored action menu — only reach forOverlaywhen you need custom anchoring or animation.- Pass
rootOverlay: trueto pin an entry above every navigator route instead of the nearest one.
Own the insert/remove pair like any other resource and Overlay becomes the most flexible layer in Flutter — forget dispose and it becomes a ghost that outlives your routes.