ToastRegion
An interactive page: C# runs over a Blazor Server connection.
Usage
@* MainLayout.razor, once for the app *@
<ToastRegion />
@* A page *@
ToastService Toasts
<form method="post" @formname="save" @onsubmit="Save">
<AntiforgeryToken />
<Button Submit>Save</Button>
</form>
{
private void Save() => Toasts.Success("Your changes are live.", "Saved");
}The region goes in the layout once, and ToastService shows toasts in it from anywhere: a form post on a static page, or a C# handler on an interactive one. AddIzzyBlazor() registers the service. A toast is for news that needs no place on the page, such as "Saved"; for a message beside what it's about, use an Alert.
Showing a toast
Use Success, Info, Warning or Error on the injected ToastService, with an optional title after the message.
Preview
Code
ToastService Toasts
@* Once per app, in the layout (the docs layout has it): <ToastRegion /> *@
<Row Gap="2" Wrap Justify="Justify.Center">
<form method="post" @formname="toast-save" @onsubmit="Save" data-enhance>
<AntiforgeryToken />
<Button Submit StartIcon="Lucide.Save">Save</Button>
</form>
<form method="post" @formname="toast-publish" @onsubmit="Publish" data-enhance>
<AntiforgeryToken />
<Button Submit Variant="ButtonVariant.Outline" Accent="Accent.Danger" StartIcon="Lucide.Send">Publish</Button>
</form>
</Row>
{
private void Save() => Toasts.Success("Your changes are live.", "Saved");
private void Publish() => Toasts.Error("The server didn't respond. Try again in a moment.", "Couldn't publish");
}Live: ToastService adds the toast in the browser straight away, with no post.
On a static page the toast is rendered in the response, so it needs no script; on an interactive page it's added in the browser at once. Show toasts in response to an action rather than in OnInitialized, which runs twice on a prerendered interactive page.
An action on the toast
Use a ToastAction for a button such as Undo or View: its Href is followed on static pages, and its OnClick runs C# on interactive ones.
Preview
Code
ToastService Toasts
NavigationManager Navigation
@* Once per app, in the layout (the docs layout has it): <ToastRegion /> *@
<Row Gap="3" Align="Align.Center" Wrap Justify="Justify.Center">
<form method="post" @formname="toast-archive" @onsubmit="Archive" data-enhance>
<AntiforgeryToken />
<Button Submit Variant="ButtonVariant.Outline" Accent="Accent.Neutral" StartIcon="Lucide.Archive">Archive</Button>
</form>
<TextBlock As="span" Size="TextSize.Sm" Color="Color.Muted">State</TextBlock>
</Row>
{
// Undo's link: this page with ?restored=1.
[SupplyParameterFromQuery(Name = "restored")]
private int? Restored { get; set; }
private bool _archived;
private bool _restored;
private string State => _archived ? "Invoice INV-1042 is archived" : _restored || Restored == 1 ? "Restored to the inbox" : "Invoice INV-1042 is in the inbox";
private void Archive()
{
_archived = true;
_restored = false;
// Static pages use the link; interactive pages run OnClick.
Toasts.Info("Invoice INV-1042 archived.", action: new("Undo", Href: Navigation.GetUriWithQueryParameter("restored", 1))
{
OnClick = () =>
{
_archived = false;
_restored = true;
StateHasChanged();
return Task.CompletedTask;
},
});
}
}Live: Undo runs C# over the connection and closes the toast.
A static page has no connection to run C# over, so a toast shown there needs an Href: one with only OnClick throws.
After a redirect
Use ToastService before NavigateTo in a static post's handler: the toast follows to the next page in a short-lived cookie, so post-redirect-get works.
Preview
Code
ToastService Toasts
NavigationManager Navigation
@* Once per app, in the layout (the docs layout has it): <ToastRegion /> *@
<form method="post" @formname="toast-redirect" @onsubmit="Send" data-enhance>
<AntiforgeryToken />
<Button Submit StartIcon="Lucide.Send">Send invoice</Button>
</form>
{
private void Send()
{
// Post, then redirect: the toast follows in a short-lived cookie.
Toasts.Success("INV-1042 is on its way to Ada Lovelace.", "Invoice sent");
Navigation.NavigateTo(Navigation.GetUriWithQueryParameter("sent", "INV-1042"));
}
}Static: The same, and the toast closes itself after 5 seconds.
On an interactive page the toast goes to the browser at once, before NavigateTo loads the next page, and stays on screen as it does. forceLoad loads a whole new page, which loses it: show it from the page you land on instead.
The inbox below posts each Archive, redirects with the archived ids in the address, and offers Undo as a link back to the address without that conversation:
Preview
Code
ToastService Toasts
NavigationManager Navigation
<ToastRegion />
<Card Title="Inbox" Description="($"{Shown.Count} conversations")" Grow MaxWidth="40.Rem">
<CardBody Flush>
if (Shown.Count == 0)
{
<EmptyState Icon="Lucide.Inbox" Title="All caught up" Description="Archived conversations stay in Archive for 30 days." Size="ControlSize.Sm" />
}
else
{
<form method="post" @formname="inbox" @onsubmit="Archive" data-enhance>
<AntiforgeryToken />
foreach (var message in Shown)
{
<Row Gap="3" Align="Align.Center" PaddingX="4" PaddingY="3" BorderTop="1">
<Avatar Name="message.From" Decorative />
<Stack Gap="0" Grow MinWidth="0.Rem">
<TextBlock Weight="FontWeight.Medium" Truncate>message.From</TextBlock>
<TextBlock Size="TextSize.Sm" Color="Color.Muted" Truncate>message.Subject</TextBlock>
</Stack>
@* On phones the button is the icon alone, which keeps "Archive" as its name. *@
<Button Submit Name="archive" Value="message.Id" OnClick="() => _clicked = message.Id" VisibleFrom="Breakpoint.Sm"
Size="ControlSize.Sm" Variant="ButtonVariant.Ghost" Accent="Accent.Neutral" StartIcon="Lucide.Archive">Archive</Button>
<IconButton Submit Name="archive" Value="message.Id" OnClick="() => _clicked = message.Id" HiddenFrom="Breakpoint.Sm" Shrink="false"
Size="ControlSize.Sm" Variant="ButtonVariant.Ghost" Accent="Accent.Neutral" Icon="Lucide.Archive" Label="Archive" />
</Row>
}
</form>
}
</CardBody>
</Card>
{
// Static pages: the archived ids live in the address (?archived=…), so each page load knows them.
[SupplyParameterFromQuery(Name = "archived")]
private string[]? ArchivedQuery { get; set; }
// The pressed button's Name=Value posts with the form.
[SupplyParameterFromForm(FormName = "inbox", Name = "archive")]
private string? Posted { get; set; }
// Interactive pages post nothing, so OnClick says which button it was.
private string? _clicked;
private HashSet<string> _archived = [];
private List<Conversation> Shown => Conversations.Where(c => !_archived.Contains(c.Id)).ToList();
protected override void OnParametersSet() => _archived = [.. ArchivedQuery ?? []];
private void Archive()
{
var conversation = Conversations.FirstOrDefault(c => c.Id == (Posted ?? _clicked));
if (conversation is null)
{
return;
}
var undo = UrlWith(_archived.Where(id => id != conversation.Id));
_archived.Add(conversation.Id);
Toasts.Info($"Archived \"{conversation.Subject}\".", action: new("Undo", Href: undo)
{
OnClick = () =>
{
_archived.Remove(conversation.Id);
StateHasChanged();
return Task.CompletedTask;
},
});
// A static post redirects to the new address, and the toast follows it.
if (!RendererInfo.IsInteractive)
{
Navigation.NavigateTo(UrlWith(_archived));
}
}
private string UrlWith(IEnumerable<string> archived) =>
Navigation.GetUriWithQueryParameters(new Dictionary<string, object?> { ["archived"] = archived.ToArray() });
}Live: Archive and Undo run C# with no page load.
How long it stays
Use Duration on the region for how long toasts stay, in milliseconds (5000; 0 keeps them open), or a toast's own Duration for one of them.
@* Toasts stay 8 seconds, 16 with an action; errors stay until closed. *@
<ToastRegion Duration="8000" />
@* This one stays until it's closed. *@
Toasts.Show(new ToastMessage("Export ready.", Status.Success, Duration: 0,
Action: new("Download", Href: "/exports/42")));By default an error stays until it's closed, and a toast with an action stays twice as long. With JavaScript off nothing closes by itself: every toast stays until its close button is used, or the next page loads.
Where they go
Use Placement for the corner or edge toasts stack in: BottomEnd by default, or TopStart, TopCenter, TopEnd, BottomStart or BottomCenter. Variant draws them as alerts are drawn (Outline by default, a raised card).
Its own words
Use Label for the region's name ("Notifications") and DismissLabel for the close buttons' name ("Dismiss"), or set them for the whole app as in Text and other languages.
Mode support
| Feature | Off | Static | Live |
|---|---|---|---|
| Toasts from a static post or page, rendered with the response | Yes | Yes | Yes |
| Kept across a redirect or NavigateTo (not forceLoad on interactive pages) | Yes | Yes | Yes |
| Close themselves after Duration, pausing on hover and focus | No | Yes | Yes |
| Added in the browser at once from C#, with no post | No | No | Yes |
| An action's OnClick runs C# | No | No | Yes |
Off: JavaScript disabled. Static: a static SSR page with IzzyBlazor's script. Live: an interactive page (Server, WebAssembly or Auto).
Accessibility
- It's a
regionlandmark named "Notifications" (Label); every toast, errors included, is read politely through onearia-live="polite"container. - By default error toasts stay until closed (a toast's own
Durationoverrides it), a toast with an action stays twiceDuration, andDuration="0"keeps every toast open. - With script, the timers pause while the mouse is over the region, while focus is in it, and while the browser tab is hidden.
- With script, the close control is a
buttonnamed "Dismiss" (DismissLabel); when a focused toast closes, focus moves to the next open toast, or back to where it came from. - A toast doesn't take focus when it appears, so an action in it should also be reachable somewhere else.
Parameters
| Prop | Type | Default |
|---|---|---|
PlacementWhere toasts stack. | ToastPlacement | ToastPlacement.BottomEnd |
DurationMilliseconds before a toast closes itself (with JavaScript). | int | 5000 |
VariantHow toasts are drawn. | AlertVariant | AlertVariant.Outline |
LabelThe region's name for screen readers. | string? | "Notifications" |
DismissLabelThe close buttons' accessible name. | string? | "Dismiss" |
See also
- Alert: a status message about part of a page.
- Banner: a full-width message about the whole app.
- Dialog: a native modal dialog, opened by a trigger, a link or C#.
- Demos: Inbox with undo.