SuperOverlay

SuperOverlay is a Flutter package for app-level overlays backed by a self-managed OverlayEntry tree. It provides command-style dialogs, loading indicators, toasts, target-attached popups, highlighted masks, notifications, route binding, widget binding, and back-button policies without requiring a package-owned Navigator key.

Quick Start

Add the package:

dependencies:
  super_overlay: ^0.2.0

SuperOverlay requires Dart >=3.7.0 <4.0.0 and Flutter >=3.29.0.

Initialize the overlay host once at the app root:

import 'package:flutter/material.dart';
import 'package:super_overlay/super_overlay.dart';

class App extends StatelessWidget {
  const App({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      builder: SuperOverlay.init(),
      navigatorObservers: [SuperOverlay.observer],
      home: const AppHome(),
    );
  }
}

Show overlays through command services and keep the returned handle when the calling flow owns the overlay lifecycle:

final loading = SuperOverlay.loading.show(message: 'Syncing...');
try {
  await syncProfile();
  SuperOverlay.toast(
    'Saved',
    options: const OverlayToastOptions(
      displayPolicy: OverlayToastDisplayPolicy.replaceLatest,
    ),
  );
} finally {
  await loading.close();
}

Dialogs return a typed result through OverlayHandle.closed:

final handle = SuperOverlay.dialog.show<bool>(
  builder: (_) => const ConfirmDeleteDialog(),
  options: const OverlayDialogOptions(
    tag: 'delete-confirmation',
    strategy: OverlayStrategy.replaceExisting,
    backBehavior: OverlayBackBehavior.dismiss,
  ),
);

final confirmed = await handle.closed;

Inside dialog content, close by handle when possible. For content that does not receive a handle, close by target and tag:

await SuperOverlay.close(
  target: OverlayCloseTarget.dialog,
  tag: 'delete-confirmation',
  result: true,
);

Production Recipes

Default Feedback Styling

Applications can configure default loading, toast, and notification rendering during initialization. Per-call builders still take precedence.

MaterialApp(
  builder: SuperOverlay.init(
    toastBuilder: (message) => AppToast(message: message),
    loadingBuilder: (message) => AppLoading(message: message),
    notifyStyle: NotifyStyle(
      successBuilder: (message) => AppBanner.success(message),
      errorBuilder: (message) => AppBanner.error(message),
    ),
  ),
  navigatorObservers: [SuperOverlay.observer],
  home: const AppHome(),
);

Loading Plus Page-Owned Empty And Error States

Use overlays for transient request state and keep durable empty or error pages inside the route that owns the data.

final loading = SuperOverlay.loading.show(
  message: 'Loading products...',
  options: const OverlayLoadingOptions(
    minimumVisibleDuration: Duration(milliseconds: 500),
    backBehavior: OverlayBackBehavior.block,
  ),
);

try {
  final products = await repository.loadProducts();
  setState(() {
    items = products;
    error = null;
  });
} catch (error) {
  setState(() {
    items = const [];
    this.error = error;
  });
  SuperOverlay.notify.error('Products could not be loaded');
} finally {
  await loading.close();
}

Anchored Popup

Attach popups to a target context for menus, filters, or lightweight editors:

final handle = SuperOverlay.popup.show<void>(
  targetContext: buttonContext,
  builder: (_) => FilterPopup(onApply: applyFilters),
  options: const OverlayPopupOptions(
    tag: 'product-filter',
    alignment: Alignment.bottomCenter,
    strategy: OverlayStrategy.replaceExisting,
  ),
);

await handle.visible;

For geometry-sensitive popups, provide typed hooks:

SuperOverlay.popup.show<void>(
  targetContext: targetContext,
  builder: (_) => const ToolbarMenu(),
  options: OverlayPopupOptions(
    tag: 'toolbar-menu',
    alignment: Alignment.bottomLeft,
    alignmentMode: OverlayPopupAlignmentMode.center,
    targetRectBuilder: (rect) => rect.inflate(4),
    replacementBuilder: (info) {
      return ToolbarMenu(width: info.popupSize.width);
    },
    adjustmentBuilder: (_) {
      return const PopupAdjustment(alignment: Alignment.topRight);
    },
    scaleOriginBuilder: (size) => Offset(size.width, 0),
  ),
);

Guided Highlight

Use popup highlighting when the user must interact with a specific target while the rest of the screen is masked.

SuperOverlay.popup.show<void>(
  targetContext: targetContext,
  builder: (_) => const GuideBubble(),
  options: OverlayPopupOptions(
    tag: 'onboarding-step',
    dismissOnMaskTap: false,
    highlightTarget: true,
    highlightMaskColor: const Color(0x99000000),
    highlightPadding: const EdgeInsets.all(8),
    highlightBorderRadius: BorderRadius.circular(8),
  ),
);

Route And Widget Binding

Dialogs and popups bind to the current route by default. They hide while another route covers that page, reappear when the page returns, and close when the route is removed. Register SuperOverlay.observer for this behavior.

Bind a dialog to a widget when the overlay must not outlive that widget:

SuperOverlay.dialog.show<void>(
  builder: (_) => const FieldHelpDialog(),
  options: OverlayDialogOptions(
    tag: 'field-help',
    bindToWidget: fieldContext,
    alignment: Alignment.bottomCenter,
    barrierColor: Colors.transparent,
    dismissOnMaskTap: false,
    consumeEvents: false,
  ),
);

Global Cleanup

Prefer handle.close() for owned overlays. Use SuperOverlay.close for global cleanup, tagged content buttons, or tests:

await SuperOverlay.close(target: OverlayCloseTarget.allToasts);

await SuperOverlay.close(
  target: OverlayCloseTarget.allDialogs,
  tag: 'checkout',
  force: true,
);

Check existence with typed surfaces:

final hasCheckoutOverlay = SuperOverlay.exists(
  tag: 'checkout',
  surfaces: const {OverlaySurface.dialog, OverlaySurface.popup},
);

Contracts And Limits

SuperOverlay exposes a command-oriented public API from package:super_overlay/super_overlay.dart. The supported entrypoints are SuperOverlay.init, SuperOverlay.observer, SuperOverlay.dialog, SuperOverlay.loading, SuperOverlay.popup, SuperOverlay.notify, SuperOverlay.toast, SuperOverlay.close, SuperOverlay.exists, typed option objects, and OverlayHandle.

OverlayHandle.visible completes when the overlay is ready for interaction. OverlayHandle.closed completes once, with the optional result. Calling close() more than once is safe.

Tags are business identifiers. Use OverlayStrategy.replaceExisting when only one overlay for a flow should exist, OverlayStrategy.keepExisting when repeated commands should reuse the active overlay, and OverlayStrategy.stack when multiple overlays are intentional.

Toasts are transient feedback. Empty pages, error pages, and durable network state belong in your application widget tree.

This package intentionally stays UI-runtime lightweight: it has no runtime dependencies beyond the Flutter SDK and does not install a global navigator key.

The current command API is a breaking public surface. Older fluent-builder, configuration-mutation, and route-key APIs are not part of the supported entrypoint.

License

SuperOverlay is MIT licensed.

Libraries

super_overlay