Veto
Components

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.

razor
@* 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.

razor
<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.

razor
@* 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.

razor
<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.

razor
<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.

razor
@* 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.

razor
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.
Something went wrong. Reload 🗙