toast_overlay 1.2.0
toast_overlay: ^1.2.0 copied to clipboard
An animated, themeable overlay toast for Flutter with an auto-dismiss countdown ring and an optional copyable support reference id.
toast_overlay #
An animated, themeable overlay toast for Flutter — with an auto-dismiss countdown ring, and an optional support reference id the user can copy.
Unlike a SnackBar, it renders into the root Overlay, so it shows above
dialogs and bottom sheets and survives route changes. It has no dependency on
your app's theme, assets or localisations — you inject those.
| Three stacked at the top | One anchored to the bottom | A reference id, with a subtitle above it |
|---|---|---|
![]() |
![]() |
![]() |
Install #
flutter pub add toast_overlay
Or add it to pubspec.yaml yourself — it is a runtime dependency:
dependencies:
toast_overlay: ^1.2.0
then:
flutter pub get
Requires package:material_ui #
Since 1.0.0 this package builds on material_ui, the standalone
Material library, rather than package:flutter/material.dart. The two declare
separate types, so your app has to be on material_ui as well — otherwise
ToastTheme cannot be registered in your ThemeData.
If your app still imports package:flutter/material.dart, migrate it with
Flutter's own fix:
dart fix --apply --code=migrate_design_widgets
Staying on package:flutter/material.dart for now? Use toast_overlay: ^0.3.0.
It needs Flutter 3.44 or newer (Dart 3.12), which is material_ui's own floor.
Use #
Initialise once with your root navigator key:
final navigatorKey = GlobalKey<NavigatorState>();
MaterialApp(navigatorKey: navigatorKey, /* … */);
Toast.init(navigatorKey: navigatorKey);
Then, from anywhere:
Toast.show(
status: ToastStatus.success,
title: 'Order placed',
subtitle: 'Your position is now open.',
);
| Parameter | Default | Meaning |
|---|---|---|
status |
— | error, success, info, warning |
title |
— | Falls back to the status default when empty |
subtitle |
null | Secondary line, up to 3 lines |
referenceId |
null | Shown with a copy button; disables auto-dismiss |
action |
null | A ToastAction button under the text — Undo, Retry |
onTap |
null | Called when the card body is tapped; dismisses after |
position |
top |
top or bottom |
offset |
kToolbarHeight |
Distance from the anchored edge |
duration |
3s | null keeps it up until dismissed |
dismissible |
true | Whether a swipe towards the edge dismisses |
pauseOnHover |
true | Whether a hovering pointer pauses the countdown |
Toast.show returns the toast's id, which Toast.dismissToast takes to remove
that one toast with its exit animation:
final id = Toast.show(status: ToastStatus.info, title: 'Uploading…', duration: null);
await upload();
Toast.dismissToast(id);
Toast.dismissAll() animates every toast out; Toast.dismiss() cuts them
immediately, without the exit animation.
Actions and taps #
Give a toast a button, and the toast dismisses itself once it is pressed:
Toast.show(
status: ToastStatus.info,
title: 'Order cancelled',
action: ToastAction(
label: 'Undo', // supply it already localised
onPressed: restoreOrder,
dismissOnPressed: true, // false keeps the toast up
),
);
onTap makes the whole card tappable — for a toast that opens the thing it is
about. The close and copy buttons keep working; they win the gesture arena.
Dismissing #
Beyond the close button, a toast is dismissed by a swipe towards its anchored
edge — up for a top toast, down for a bottom one. A drag past 40% of the card's
height, or a flick, sends it away; anything less springs back. Dragging the
other way does nothing, so it never fights a scroll underneath. Set
dismissible: false to pin a toast to the close button alone.
On desktop and web a pointer resting on the card pauses the countdown and
resumes it on the way out, so a toast does not vanish mid-sentence. A touch
pointer never hovers, so pauseOnHover costs nothing on mobile.
Stacking #
By default each toast replaces the one on screen. Raise maxStack and they
stack against their edge instead — the oldest drops off once the limit is hit:
Toast.init(
navigatorKey: navigatorKey,
maxStack: 3, // 1 (the default) replaces instead of stacking
stackSpacing: 8, // gap between two cards
);
Top-anchored and bottom-anchored toasts stack separately, each against its own
edge, and only the card nearest the edge keeps its offset.
Reference ids #
When something fails you often want to hand the user something to quote to
support. Pass
a referenceId and the toast renders Ref: <id> with a copy button — and
stops auto-dismissing, because a toast that vanishes while you are copying
it is useless. It sits under the subtitle when you pass both, and works on
any status, not only error.
Toast.show(
status: ToastStatus.error,
title: 'Withdrawal failed',
referenceId: response.traceId,
);

Theming #
Register a ToastTheme extension and the toast follows your light and dark
themes:
MaterialApp(
theme: ThemeData(extensions: const [ToastTheme(
surface: Color(0xFFF7F8FA),
borderColor: Color(0xFFE5E7EB),
titleColor: Color(0xFF0A0C12),
subtitleColor: Color(0xFF6B7280),
closeIconColor: Color(0xFF9CA3AF),
statusColors: {
ToastStatus.success: ToastStatusColors(
background: Color(0xFFD1FADF),
foreground: Color(0xFF12B76A),
),
// …
},
)]),
);
If you register nothing, it still works — the palette is derived from the
ambient ColorScheme.
Corners #
cardRadius and iconRadius are BorderRadius, so corners can differ — 12 and
6 all round by default:
ToastTheme(
// …
cardRadius: BorderRadius.circular(20),
iconRadius: const BorderRadius.only(
topLeft: Radius.circular(16),
bottomRight: Radius.circular(16),
),
);
Pass cardShape or iconShape instead when you want a shape of your own; they
override the radii.
Type #
fontFamily swaps the typeface on every line. For finer control, titleStyle,
subtitleStyle and referenceStyle are used exactly as given — a weight,
colour or family you set there is never overwritten:
ToastTheme(
// …
fontFamily: 'Inter',
titleStyle: TextStyle(fontSize: 15, fontWeight: FontWeight.w700),
subtitleStyle: TextStyle(fontSize: 13, height: 1.3),
referenceStyle: TextStyle(fontSize: 12, letterSpacing: 0.4),
);
Everything else #
ToastTheme also carries shadows, icons, maxWidth, the action button's
actionColor and actionStyle, and a glowBuilder for painting a decorative
backdrop behind the card:
glowBuilder: (context, status) => DecoratedBox(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
stops: const [0.08, 0.85],
colors: [tint.withValues(alpha: 0), tint.withValues(alpha: 0.09)],
),
),
),
Return whatever you like — an Image.asset works too, but a gradient costs no
decode, no texture upload and no image cache, which is worth having on a layer
that is purely decorative. The glows in the screenshots above ship with the
example, not the package — see example/lib/example_toast_theme.dart.
Localisation #
The package ships English defaults and takes the rest from you, so it never needs to own your ARB files:
Toast.init(
navigatorKey: navigatorKey,
strings: ToastStrings(
error: l10n.toastTypeError,
success: l10n.toastTypeSuccess,
info: l10n.toastTypeInfo,
warning: l10n.toastTypeWarning,
referencePrefix: l10n.refPrefix,
closeLabel: l10n.dismissNotification,
copyReferenceLabel: l10n.copyReferenceId,
),
);
The two labels are the accessibility labels on the close and copy buttons. The close button keeps a 48×48 tap target — it is laid out over the card, so the room costs the toast no height.
Without the global facade #
Toast is a convenience wrapper. Inject a ToastController instead if you
prefer explicit dependencies — it is what the widget tests use:
final controller = ToastController(
overlayResolver: () => navigatorKey.currentState?.overlay,
strings: const ToastStrings(),
logger: debugPrintToast,
);
controller.show(const ToastConfig(
status: ToastStatus.info,
title: 'Hello',
));
overlayResolver returning null makes show a safe no-op, which is what you
want before the first route is mounted. The toast is still recorded in history,
so nothing is silently lost.
History and logging #
The controller keeps the last 20 toasts for a debug screen, and can forward each one to your own logger:
Toast.init(navigatorKey: navigatorKey, logger: myLogger.info);
for (final entry in Toast.history.entries) {
print(entry); // 14:03:21.881 [error] Withdrawal failed — Please contact support.
}
Accessibility #
- The title and subtitle are one live region, so a screen reader reads them as a unit. On platforms that support announcements the toast is also announced when it appears — assertively for an error, politely otherwise — because a toast in an overlay is otherwise easy to miss before it auto-dismisses.
- The close and copy buttons are laid out small but keep a 48dp tap target, and
carry the labels from
ToastStrings. - The swipe is excluded from semantics: the close button is the accessible way out, and a drag handler would merge the card into one unusable node.
Behaviour notes #
- Showing a toast replaces any toast already on screen, unless
maxStackis above 1. - The card is capped at
ToastTheme.maxWidth(520 by default) and centred, so it does not stretch across a desktop window.double.infinityrestores the full-width card. - The countdown ring is only drawn while a toast is auto-dismissing.
- The card is wrapped in a
RepaintBoundaryand passed as thechildof itsAnimatedBuilder, so the animation does not rebuild the content. - The toast respects
SafeAreaon the edge it is anchored to.
License #
MIT



