Toast
Short-lived notifications raised from anywhere in the app: six positions, eleven animations, stacking, de-duplication and toasts that refuse to leave.
Raising a toast
Inject IVetoToastService and call it. One VetoToastHost in the layout does the rendering, so nothing that raises a toast needs to know anything about how it looks.
@* Once, in the layout: *@
<VetoToastHost />
@* Anywhere else: *@
@inject IVetoToastService Toasts
<VetoButton OnClick="Save">Save</VetoButton>
@code {
// A named method, not an inline lambda: Razor cannot parse a string literal
// inside an attribute-bound lambda, so `OnClick="() => Toasts.Success("Saved")"`
// will not compile however the quotes are escaped.
private void Save() => Toasts.Success("Saved");
}Title, custom icon, and a click handler
Everything on a toast is an option. A title gives the message a heading; OnClick makes the whole toast actionable; OnClose runs once it has gone, whichever way it went.
<VetoButton OnClick="Finished">Finish the build</VetoButton>
@code {
private void Finished() => Toasts.Success(
"Three files changed, none broke.",
new VetoToastOptions
{
Title = "Build finished",
OnClose = () => { _dismissed = true; return Task.CompletedTask; },
});
}Six positions
The host sets the app's position; any single toast may override it. Mixing positions across a whole app is usually a mistake, but one message in the opposite corner is sometimes exactly right.
@* The app's default, once in the layout: *@
<VetoToastHost Position="VetoToastPosition.BottomRight" />
@* One message somewhere else: *@
<VetoButton OnClick="Elsewhere">Raise it top centre</VetoButton>
@code {
private void Elsewhere() => Toasts.Info("Over here", new VetoToastOptions
{
Position = VetoToastPosition.TopCenter,
});
}Eleven animations
A toast always leaves the way it came in - the same displaced state is used for both directions, so it can never enter one way and exit another. Under prefers-reduced-motion every one of them degrades to a plain fade.
<VetoToastHost Animation="VetoToastAnimation.SlideRight" />
<VetoButton OnClick="Flip">Flip one in</VetoButton>
@code {
private void Flip() => Toasts.Info("Flipped", new VetoToastOptions
{
Animation = VetoToastAnimation.FlipXUp,
});
}Timing, the progress bar, and toasts that stay
Duration is per toast. The progress bar counts the remaining time down and holds while the pointer is over the toast or keyboard focus is inside it, so a message is never snatched away mid-read. Sticky drops the timer entirely - the toast stays until somebody dismisses it, which is what you want for anything the user must actually see.
<VetoButton OnClick="Quick">One second</VetoButton>
<VetoButton OnClick="Stay">Sticky</VetoButton>
@code {
private void Quick()
=> Toasts.Info("Gone in a second", new VetoToastOptions { Duration = 1000 });
// Sticky: no timer, no progress bar, stays until dismissed.
private void Stay() => Toasts.Error(
"This one is not going anywhere.",
new VetoToastOptions
{
Title = "Needs your attention",
Sticky = true,
});
}Duplicates and the stack limit
Press the same button twice. Allow shows both; Block drops the second; Replace folds it into the one already on screen, restarts its timer and counts the repeat - which is what you want when the repetition itself is the information. The host's MaxToasts caps how many can be on screen at once; raising one beyond the cap retires the oldest, though never a sticky one.
@* The app's rule, once in the layout: *@
<VetoToastHost MaxToasts="5" Duplicates="VetoToastDuplicates.Block" />
<VetoButton OnClick="Lost">Connection lost</VetoButton>
@code {
// Or per toast - here the repeat is the information, so count it rather
// than dropping it.
private void Lost() => Toasts.Error("Connection lost", new VetoToastOptions
{
Duplicates = VetoToastDuplicates.Replace,
});
}Following a task
TrackAsync shows a sticky toast while a task runs and swaps it for a success or an error toast when the task finishes. The exception still reaches your code. The generic overload builds the success message from the task's result.
await Toasts.TrackAsync(SaveAsync(), "Saving…", "Saved", "Could not save");
var order = await Toasts.TrackAsync(
PlaceOrderAsync(), "Placing order…", o => $"Order {o.Number} placed", "Order failed");VetoToastHost parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
Position |
VetoToastPosition |
TopRight |
TopLeft, TopCenter, TopRight, BottomLeft, BottomCenter or BottomRight. |
Animation |
VetoToastAnimation |
Fade |
None, Fade, Zoom, SlideLeft/Right/Up/Down, FlipXDown/Up, FlipYLeft/Right. |
Duration |
int |
5000 |
How long a toast stays, in milliseconds. |
AnimationDuration |
int |
250 |
How long the arrival and departure animations run. |
MaxToasts |
int |
5 |
The most toasts on screen at once. Zero means no limit. |
Duplicates |
VetoToastDuplicates |
Allow |
Allow, Block, or Replace a message already on screen. |
ShowProgress |
bool |
true |
Count each toast's remaining time down with a bar. |
ShowCloseButton |
bool |
true |
Give each toast a close button. |
CloseOnClick |
bool |
false |
Dismiss a toast when it is clicked anywhere. |
PauseOnHover |
bool |
true |
Hold the countdown while the pointer is over a toast. |
NewestOnTop |
bool |
true |
Put the newest toast nearest the screen edge. |
Size |
VetoSize |
Medium |
ExtraSmall, Small, Medium, Large or ExtraLarge. |
AriaLabel / CloseLabel |
string |
"Notifications" / "Dismiss" |
Accessible names for the stack and the close button. |
VetoToastOptions
| Parameter | Type | Default | Description |
|---|---|---|---|
Title |
string? |
null |
A heading above the message. |
Sticky |
bool |
false |
Never hide - stays until dismissed. Drops the progress bar. |
Duration |
int? |
null |
Override the host's duration for this toast. |
Position / Animation |
enum? |
null |
Override the host's position or animation for this toast. |
ShowProgress / ShowCloseButton / CloseOnClick / PauseOnHover |
bool? |
null |
Override the matching host setting. |
Icon / HideIcon |
string? / bool |
null / false |
A custom icon, or no icon at all. |
Content |
RenderFragment? |
null |
Markup rendered in place of the plain message. |
Class / Style |
string? |
null |
A bespoke look for one toast. |
TestId |
string? |
null |
Rendered as data-testid - the stable hook for a Playwright spec. |
DeduplicationKey |
string? |
null |
What counts as 'the same toast'. Defaults to type + title + message. |
Duplicates |
VetoToastDuplicates? |
null |
Override the host's duplicate policy for this toast. |
OnClick / OnClose |
Func<Task>? |
null |
Raised when the toast is clicked, and once it has gone. |
Size |
VetoSize? |
null |
Override the host's size for this toast. |