Toast
Non-blocking notifications stacked in a fixed region. The left border accent communicates message type.
Variants
SUCCESS / ERROR / WARNING / INFO
UPLINK_RESTORED
CONNECTION_LOST
HIGH_LATENCY
SYNC_COMPLETE
<div class="nf-toast nf-toast-success">
<div class="nf-toast-content">
<div class="nf-toast-title">UPLINK_RESTORED</div>
<div class="nf-toast-message">Connection re-established. Latency: 12ms.</div>
</div>
<button class="nf-toast-close" aria-label="Dismiss">✕</button>
</div>
<div class="nf-toast nf-toast-error">
<div class="nf-toast-content">
<div class="nf-toast-title">CONNECTION_LOST</div>
<div class="nf-toast-message">Node 047 unreachable. Retrying in 5s.</div>
</div>
<button class="nf-toast-close" aria-label="Dismiss">✕</button>
</div>
<div class="nf-toast nf-toast-warning">
<div class="nf-toast-content">
<div class="nf-toast-title">HIGH_LATENCY</div>
<div class="nf-toast-message">Uplink degraded. Current latency: 420ms.</div>
</div>
<button class="nf-toast-close" aria-label="Dismiss">✕</button>
</div>
<div class="nf-toast nf-toast-info">
<div class="nf-toast-content">
<div class="nf-toast-title">SYNC_COMPLETE</div>
<div class="nf-toast-message">48 records synchronized with remote node.</div>
</div>
<button class="nf-toast-close" aria-label="Dismiss">✕</button>
</div>Usage
Append toasts into a fixed .nf-toast-region live region (present in the page before the first toast, so screen readers pick up insertions), then remove them after a timeout:
<div class="nf-toast-region nf-toast-top-right" id="toast-region" aria-live="polite"></div>
function showToast({ title, message, type = 'info', duration = 4000 }) {
const region = document.getElementById('toast-region');
const toast = document.createElement('div');
toast.className = `nf-toast nf-toast-${type}`;
// Errors interrupt; everything else waits for the polite region.
if (type === 'error') toast.setAttribute('role', 'alert');
// Build with textContent, never innerHTML: titles and messages often
// contain user or server data.
const content = document.createElement('div');
content.className = 'nf-toast-content';
const heading = document.createElement('div');
heading.className = 'nf-toast-title';
heading.textContent = title;
const body = document.createElement('div');
body.className = 'nf-toast-message';
body.textContent = message;
content.append(heading, body);
const close = document.createElement('button');
close.type = 'button';
close.className = 'nf-toast-close';
close.setAttribute('aria-label', 'Dismiss');
close.textContent = '✕';
toast.append(content, close);
let timer;
let removed = false;
const remove = () => {
if (removed) return;
removed = true;
toast.remove();
};
const dismiss = () => {
clearTimeout(timer);
toast.classList.add('nf-toast-dismissing');
toast.addEventListener('animationend', remove, { once: true });
setTimeout(remove, 1000); // fallback if animations are disabled
};
const start = () => { clearTimeout(timer); timer = setTimeout(dismiss, duration); };
const pause = () => clearTimeout(timer);
// Let people stop the timer while reading or interacting (WCAG 2.2.1).
toast.addEventListener('mouseenter', pause);
toast.addEventListener('mouseleave', start);
toast.addEventListener('focusin', pause);
toast.addEventListener('focusout', start);
close.addEventListener('click', dismiss);
region.appendChild(toast);
start();
return toast;
}
Region positions
| Class | Position |
|---|---|
nf-toast-top-right |
Top-right (default) |
nf-toast-top-left |
Top-left |
nf-toast-bottom-right |
Bottom-right |
nf-toast-bottom-left |
Bottom-left |
Class reference
| Class | Description |
|---|---|
nf-toast-region |
Fixed container — stacks toasts vertically |
nf-toast |
Individual notification |
nf-toast-success |
Primary green left border |
nf-toast-error |
Error red left border |
nf-toast-warning |
Secondary magenta left border |
nf-toast-info |
Tertiary purple left border |
nf-toast-dismissing |
Triggers the slide-out exit animation |
nf-toast-content |
Flex column wrapper for title + message |
nf-toast-title |
Label-font heading |
nf-toast-message |
Body text |
nf-toast-close |
Dismiss button |