Toast
Queued, nonmodal notifications with timers, pause rules, and status or alert announcements
Short, nonmodal notifications that appear at a screen edge, stack, and dismiss themselves.
When to use this
- Confirmations: "Draft saved", "Link copied"
- Undo affordances: A short-lived message with one action
- Background results: An upload or sync that finished while the user kept working
- Urgent notices: Failures that must be announced immediately
Use a dialog instead when the user must respond before continuing.
Basic implementation
Place one RemixToastScope above your app's Navigator, so every route, including dialogs, can reach it. With WidgetsApp, put it in builder inside Overlay.wrap, which gives toasts their own overlay above every route. No Scaffold is required.
Scope placement
import 'package:flutter/widgets.dart';
import 'package:remix/remix.dart';
import 'ui/ui.dart';
class App extends StatelessWidget {
const App({super.key});
@override
Widget build(BuildContext context) {
return WidgetsApp(
color: const Color(0xFFF8FAFC),
pageRouteBuilder: <T>(settings, builder) => PageRouteBuilder<T>(
settings: settings,
pageBuilder: (context, animation, secondaryAnimation) => builder(context),
),
// Theme and toast scopes belong in `builder`, above the Navigator, so
// every route, including dialogs, can reach them.
builder: (context, child) => FortalScope(
child: Overlay.wrap(
child: RemixToastScope(style: fortalToastStyle(), child: child!),
),
),
home: const Center(child: FortalText('Home')),
);
}
}Show a toast from any event callback below the scope:
Showing a toast
import 'package:flutter/widgets.dart';
import 'package:remix/remix.dart';
import 'ui/ui.dart';
class SaveButton extends StatelessWidget {
const SaveButton({super.key, required this.onSave, required this.onUndo});
final VoidCallback onSave;
final VoidCallback onUndo;
@override
Widget build(BuildContext context) {
return FortalButton(
label: 'Save',
onPressed: () {
onSave();
showRemixToast(
context,
RemixToastData(
title: 'Draft saved',
description: 'Stored on this device.',
action: RemixToastAction(label: 'Undo', onPressed: onUndo),
),
);
},
);
}
}showRemixToast returns a RemixToastHandle. Await handle.closed to learn why the toast left (timeout, action, close, programmatic, replaced, queueOverflow, or scopeDisposed), or call handle.dismiss() to remove it early.
Behavior
- Queue. Up to
maxVisibletoasts (default 3) show at once, oldest first. Up tomaxQueuedmore (default 20) wait; when the queue is full the oldest waiting toast is dropped withqueueOverflow. A waiting toast has no widget, announcement, or timer. - Lifetime. Each toast stays for its
duration(default 4 seconds) once it is visible. Anullduration persists until dismissed and requires an action or the close button. - Pausing. The countdown stops while the pointer is over the toast, while focus is inside it, and while the app is in the background. Resuming restarts the full duration.
- Replacement. Showing
RemixToastDatawith the same non-nullidas a visible or queued toast updates it in place and restarts its lifetime; the earlier handle closes withreplaced. - Placement. Six
RemixToastPlacementvalues. Start and end follow the text direction, and the newest toast sits nearest the edge. The stack clears the safe area and the keyboard. - Theme. Toasts render through one overlay portal, so they inherit the scope's
MixScopetokens and update live when the theme changes.
Accessibility
- Each toast is announced once, as a status (
RemixToastPriority.polite, the default) or an alert (RemixToastPriority.assertive). The announcement issemanticLabel, or the title and description on separate lines. - Reserve
assertivefor urgent, destructive, or time-sensitive messages. A Fortalintentchanges only the icon color, never the announcement. - Showing a toast never moves focus. The action and close button are ordinary buttons in the focus order; Escape dismisses the toast that contains focus, and focus returns to where it was.
- When the platform reports accessible navigation (
MediaQuery.accessibleNavigationOf), toasts with an action or close button never time out. - Set
RemixToastScope.dismissLabelto a localized close-button label.
Fortal widgets
The application-owned Fortal preset provides fortalToastStyle, a recipe for the scope, and the generated FortalToast for static previews:
fortalToastStyle defaults to FortalToastVariant.classic, FortalToastSize.size2, and FortalToastIntent.accent. The surface caps at 360 logical pixels and shrinks on narrow screens. Toast is a Fortal extension: Radix Themes has no toast, so the recipe reuses the Card panel and shadow tokens.
Fortal recipe
import 'package:flutter/widgets.dart';
import 'package:remix/remix.dart';
import 'ui/ui.dart';
void showUploadFailed(BuildContext context) {
showRemixToast(
context,
RemixToastData(
title: 'Upload failed',
priority: RemixToastPriority.assertive,
style: fortalToastStyle(intent: FortalToastIntent.error),
),
);
}See the fortalToastStyle source code for all available options.
Show function
showRemixToast
import 'package:flutter/widgets.dart';
import 'package:remix/remix.dart';
RemixToastHandle showRemixToastSignature(
BuildContext context,
RemixToastData toast,
) => throw UnimplementedError();Throws a FlutterError when there is no RemixToastScope above context, and an ArgumentError for blank text or a persistent toast without an action or close button. Call it from an event callback, not during build.
With a caller-owned RemixToastController, use controller.showToast(data) instead.
Constructors
RemixToastScope
import 'package:flutter/widgets.dart';
import 'package:remix/remix.dart';
RemixToastScope remixToastScopeConstructor({
Key? key,
RemixToastController? controller,
RemixToastPlacement placement = RemixToastPlacement.bottomEnd,
int maxVisible = 3,
int maxQueued = 20,
EdgeInsetsGeometry inset = const EdgeInsetsDirectional.all(24),
double gap = 12,
String dismissLabel = 'Dismiss notification',
ToastStyler style = const ToastStyler.create(),
required Widget child,
}) => throw UnimplementedError();RemixToastData
import 'package:flutter/widgets.dart';
import 'package:remix/remix.dart';
RemixToastData remixToastDataConstructor({
Object? id,
required String title,
String? description,
String? semanticLabel,
IconData? icon,
RemixToastAction? action,
Duration? duration = const Duration(seconds: 4),
RemixToastPriority priority = RemixToastPriority.polite,
bool showCloseButton = true,
ToastStyler? style,
}) => throw UnimplementedError();RemixToast
import 'package:flutter/widgets.dart';
import 'package:remix/remix.dart';
RemixToast remixToastConstructor({
Key? key,
required String title,
String? description,
IconData? icon,
RemixToastAction? action,
VoidCallback? onDismiss,
String? dismissLabel,
bool excludeMessageSemantics = false,
ToastStyler style = const ToastStyler.create(),
ToastSpec? styleSpec,
}) => throw UnimplementedError();RemixToast is the stateless surface the scope builds for each toast. Build it directly for a custom presenter or a static preview.
Properties
Scope Properties
controller → RemixToastController?
Optional. A caller-owned controller. The scope creates its own when this is null and never disposes one you pass in.
placement → RemixToastPlacement
Optional. The edge and side the stack grows from. Defaults to bottomEnd.
maxVisible → int
Optional. How many toasts can be on screen at once. At least one.
maxQueued → int
Optional. How many toasts can wait for a visible slot.
inset → EdgeInsetsGeometry
Optional. Space between the stack and the safe area's edges.
gap → double
Optional. Space between stacked toasts.
dismissLabel → String
Optional. The localized accessible name of every close button. Must not be blank.
style → ToastStyler
Optional. The base style of every toast. Each RemixToastData.style merges over it.
Style Methods
container(FlexBoxStyler value)
Configures the outer row: surface, padding, and the gap between icon, message, and controls.
content(FlexBoxStyler value)
Configures the column holding the title and description.
title(TextStyler value)
Configures the title text style.
description(TextStyler value)
Configures the description text style.
icon(IconStyler value)
Configures the decorative leading icon.
action(Style<ButtonSpec> value)
Styles the action button. The style resolves against the button's own hover, focus, and press states.
closeButton(Style<IconButtonSpec> value)
Styles the close button, resolved against its own states.