Connection lost. Reconnecting… attempt 1 of 8
Paused. Your work is held on the server.
Could not reconnect.
This session has expired on the server.
DR.Simple_UI
Showing main. This can be ahead of the version your app has installed — check what a class says it needs before copying it. Releases (opens in a new tab)

Toasts

tier 2 — classes

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

Order dispatchedORD-4182 is on its way to EU-West.
Partially fulfilledTwo of five lines are awaiting stock.
Import failedThe endpoint returned 503. Nothing was written.
Nightly reconciliation closed 4 stale orders.
<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.

Order dispatchedORD-4182 is on its way to EU-West.

Something to do — an alert, until the state changes.

The import wrote nothing. 41 of 128 rows were rejected.
<div class="dr-col dr-gap-3" style="max-width:460px">
    <div>
        <p class="form-hint dr-mb-1">Nothing to do &mdash; 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 &mdash; 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.