Toast
Transient notifications that stack and auto-dismiss. Each toast schedules a
timer to remove itself; the timers are tracked per id and cleared in
onDestroy so nothing leaks. The list renders through data-each.
Markup
<button @click="success">Show success</button>
<button @click="error">Show error</button>
<div class="toast-stack" aria-live="polite">
<template data-each="toasts" data-key="id">
<div data-bind="class:toastClass(item.kind)">
<span data-text="item.msg"></span>
<button @click="dismiss" data-bind="data-id:item.id" aria-label="Dismiss">
×
</button>
</div>
</template>
</div>
Component
Micra.define("toast", {
state: { toasts: [] },
success() {
this.push("success", "Changes saved");
},
error() {
this.push("error", "Something went wrong");
},
push(kind, msg) {
this._n = (this._n || 0) + 1;
const id = this._n;
this.state.toasts = [...this.state.toasts, { id, kind, msg }];
this._timers = this._timers || {};
this._timers[id] = setTimeout(() => this.dismissId(id), 3200);
},
dismiss(e) {
this.dismissId(Number(e.currentTarget.dataset.id));
},
dismissId(id) {
clearTimeout(this._timers?.[id]);
this.state.toasts = this.state.toasts.filter((t) => t.id !== id);
},
toastClass(kind) {
return kind === "success" ? "toast toast-success" : "toast toast-error";
},
onDestroy() {
for (const id in this._timers || {}) clearTimeout(this._timers[id]);
},
});
Pushing a toast replaces the toasts array (not a mutation), so the keyed
data-each adds exactly one row. Removal filters by id and clears that toast’s
timer.