Toasts
tier 2 — classesA confirmation that appears, says what happened, and goes away. Anything the reader still has to act on is an alert, which stays until the state changes.
Raising one
Live. drSimpleUi.toast(message, options) renders the stack and
the toast, stacks a second under the first, and removes it after its timeout.
timeout: 0 keeps it until dismissed, which is what a failure wants. From C# it
is ToastAsync.
The return value is a function that removes that toast early. It stays JavaScript-only, because a function cannot cross the interop boundary.
kind is go, warn, danger or
info, and info is the default. Pick by what the reader should do
next, not by how the message feels.
<button class="btn btn-go" type="button"
onclick="drSimpleUi.toast('ORD-4182 is on its way to EU-West.', { kind: 'go', title: 'Order dispatched' })">
Dispatch
</button>
<button class="btn btn-warn" type="button"
onclick="drSimpleUi.toast('Two of five lines are awaiting stock.', { kind: 'warn', title: 'Partially fulfilled' })">
Warn
</button>
<button class="btn btn-danger" type="button"
onclick="drSimpleUi.toast('The endpoint returned 503. Nothing was written.', { kind: 'danger', title: 'Import failed', timeout: 0 })">
Fail, stays until dismissed
</button>
<button class="btn" type="button"
onclick="drSimpleUi.toast('Nightly reconciliation closed 4 stale orders.')">
Info
</button>
The markup
You do not write this for drSimpleUi.toast — it is here for an app that renders
the stack itself, from server state or as part of a page's own model.
.toast-stack is the only positioned element: fixed to the
bottom-inline-end corner at z-index 600. Each toast is a plain block inside it, so an app
renders a list and positions nothing. The stack here is position:static so it
can be seen in place rather than pinned to the corner of the screen.
Do not put role="alert" on a toast. The library adds the live region when it
creates the stack; a second one announces the same text twice.
<div class="toast-stack" style="position:static; inset:auto;">
<div class="toast toast-go">
<i class="ri-check-line"></i>
<span class="toast-body"><strong>Order dispatched</strong>ORD-4182 is on its way to EU-West.</span>
<button class="toast-close" aria-label="Dismiss"><i class="ri-close-line"></i></button>
</div>
<div class="toast toast-warn">
<i class="ri-alert-line"></i>
<span class="toast-body"><strong>Partially fulfilled</strong>Two of five lines are awaiting stock.</span>
<button class="toast-close" aria-label="Dismiss"><i class="ri-close-line"></i></button>
</div>
<div class="toast toast-danger">
<i class="ri-error-warning-line"></i>
<span class="toast-body"><strong>Import failed</strong>The endpoint returned 503. Nothing was written.</span>
<button class="toast-close" aria-label="Dismiss"><i class="ri-close-line"></i></button>
</div>
<div class="toast toast-info">
<i class="ri-information-line"></i>
<span class="toast-body">Nightly reconciliation closed 4 stale orders.</span>
<button class="toast-close" aria-label="Dismiss"><i class="ri-close-line"></i></button>
</div>
</div>
Toast or alert
The same event, told both ways. A toast is a receipt for something that already happened and needs nothing from the reader. An alert stays until the state changes, and is the only one of the two that may carry an action.
A toast with a button in it is an action nobody performs: it is gone in four seconds, and it is announced politely to anybody who cannot see it.
Nothing to do — a toast, gone in a few seconds.
Something to do — an alert, until the state changes.
<div class="dr-col dr-gap-3" style="max-width:460px">
<div>
<p class="form-hint dr-mb-1">Nothing to do — a toast, gone in a few seconds.</p>
<div class="toast-stack" style="position:static; inset:auto">
<div class="toast toast-go">
<i class="ri-check-line"></i>
<span class="toast-body"><strong>Order dispatched</strong>ORD-4182 is on its way to EU-West.</span>
<button class="toast-close" aria-label="Dismiss"><i class="ri-close-line"></i></button>
</div>
</div>
</div>
<div>
<p class="form-hint dr-mb-1">Something to do — an alert, until the state changes.</p>
<div class="alert alert-danger">
<i class="ri-error-warning-line"></i>
<span>The import wrote nothing. 41 of 128 rows were rejected.</span>
<button class="btn btn-sm dr-push" type="button">Review the rejects</button>
</div>
</div>
</div>
Raising one
timeout: 0 keeps a toast until it is dismissed, which is what a failure the
reader has to read needs. The JavaScript call returns a function that removes that toast
early; the C# wrapper cannot, because a callback does not cross the interop boundary — keep a
withdrawable toast in JavaScript.
// JavaScript. Returns a function that removes this toast early.
const close = drSimpleUi.toast('ORD-4182 is on its way to EU-West.', {
kind: 'go', // 'go' | 'warn' | 'danger' | 'info', default neutral
title: 'Order dispatched',
timeout: 4000, // 0 stays until dismissed
dismissible: true, // false removes the close button
});
close(); // e.g. once the SignalR event confirms it
// C#. The remover cannot cross the JS boundary, so ToastAsync returns nothing.
// For a toast the app has to be able to withdraw, keep it in JavaScript.
await Ui.ToastAsync("ORD-4182 is on its way to EU-West.",
ToastKind.Go, title: "Order dispatched");
// A failure the reader has to see gets timeout: 0. It is announced through
// aria-live on the stack — assertive for danger, polite for everything else —
// so nothing steals focus from what is being typed.
await Ui.ToastAsync("The endpoint returned 503. Nothing was written.",
ToastKind.Danger, title: "Import failed", timeoutMs: 0);
// The stack is created on first use and found by data-dr-toasts, so an app
// renders nothing and positions nothing. Only a stack the library created is
// ever appended to or removed.