Overview
The toast widget displays non-blocking notification messages in a corner of the screen. Toasts support auto-dismiss, action buttons, progress indicators, and multiple toast stacking.
Basic Usage
import { ui } from "@rezi-ui/core";
import { defineWidget } from "@rezi-ui/core";
const ToastExample = defineWidget((ctx) => {
const [toasts, setToasts] = ctx.useState<Toast[]>([]);
const showToast = (message: string, type: Toast["type"]) => {
const toast: Toast = {
id: Date.now().toString(),
message,
type,
duration: 3000,
};
setToasts([...toasts, toast]);
};
const dismissToast = (id: string) => {
setToasts(toasts.filter((t) => t.id !== id));
};
return ui.layers([
ui.column({ gap: 1, p: 1 }, [
ui.button({
id: "toast-info",
label: "Show Info",
onPress: () => showToast("This is an info message", "info"),
}),
ui.button({
id: "toast-success",
label: "Show Success",
onPress: () => showToast("Action completed successfully", "success"),
}),
ui.button({
id: "toast-warning",
label: "Show Warning",
onPress: () => showToast("Warning: Check your settings", "warning"),
}),
ui.button({
id: "toast-error",
label: "Show Error",
onPress: () => showToast("Error: Failed to save", "error"),
}),
]),
ui.toastContainer({
toasts,
position: "bottom-right",
onDismiss: dismissToast,
}),
]);
});
Props
ToastContainerProps
Active toast notifications to display.
onDismiss
(id: string) => void
required
Callback when a toast is dismissed (auto or manual).
position
ToastPosition
default:"'bottom-right'"
Position on screen:
"top-left"
"top-center"
"top-right"
"bottom-left"
"bottom-center"
"bottom-right"
Maximum visible toasts. Older toasts are hidden when limit reached.
Toast container width in cells.
Frame/surface colors for toast backgrounds, text, and borders.
Toast Object
type
'info' | 'success' | 'warning' | 'error'
required
Toast type determines color and icon.
Auto-dismiss duration in milliseconds. Set to 0 for persistent toast.
Optional action button with label and onAction callback.
Optional progress indicator (0-100).
Toast Types
Info Toast
const toast: Toast = {
id: "info-1",
message: "Your profile has been updated",
type: "info",
duration: 3000,
};
Success Toast
const toast: Toast = {
id: "success-1",
message: "File saved successfully",
type: "success",
duration: 3000,
};
Warning Toast
const toast: Toast = {
id: "warning-1",
message: "Disk space running low",
type: "warning",
duration: 5000,
};
Error Toast
const toast: Toast = {
id: "error-1",
message: "Failed to connect to server",
type: "error",
duration: 5000,
};
Persistent Toasts
Set duration: 0 for toasts that don’t auto-dismiss:
const toast: Toast = {
id: "persistent-1",
message: "Manual dismissal required",
type: "info",
duration: 0, // Won't auto-dismiss
};
const toast: Toast = {
id: "action-1",
message: "New version available",
type: "info",
duration: 0,
action: {
label: "Update",
onAction: () => {
startUpdate();
dismissToast("action-1");
},
},
};
Progress Indicators
import { defineWidget } from "@rezi-ui/core";
const UploadProgress = defineWidget((ctx) => {
const [progress, setProgress] = ctx.useState(0);
const [toasts, setToasts] = ctx.useState<Toast[]>([]);
const startUpload = () => {
const uploadToast: Toast = {
id: "upload-1",
message: "Uploading file...",
type: "info",
duration: 0,
progress: 0,
};
setToasts([uploadToast]);
// Simulate progress
const interval = setInterval(() => {
setProgress((p) => {
const newProgress = p + 10;
if (newProgress >= 100) {
clearInterval(interval);
setToasts([
{
id: "upload-complete",
message: "Upload complete",
type: "success",
duration: 3000,
},
]);
return 100;
}
// Update toast progress
setToasts([
{
id: "upload-1",
message: "Uploading file...",
type: "info",
duration: 0,
progress: newProgress,
},
]);
return newProgress;
});
}, 300);
};
return ui.layers([
ui.button({
id: "upload-btn",
label: "Upload File",
onPress: startUpload,
}),
ui.toastContainer({
toasts,
position: "bottom-right",
onDismiss: (id) => setToasts(toasts.filter((t) => t.id !== id)),
}),
]);
});
Position Variants
// Top positions
ui.toastContainer({
toasts,
position: "top-left",
onDismiss: dismissToast,
});
ui.toastContainer({
toasts,
position: "top-center",
onDismiss: dismissToast,
});
ui.toastContainer({
toasts,
position: "top-right",
onDismiss: dismissToast,
});
// Bottom positions
ui.toastContainer({
toasts,
position: "bottom-left",
onDismiss: dismissToast,
});
ui.toastContainer({
toasts,
position: "bottom-center",
onDismiss: dismissToast,
});
ui.toastContainer({
toasts,
position: "bottom-right", // Default
onDismiss: dismissToast,
});
Toast Queue Management
import { defineWidget } from "@rezi-ui/core";
const ToastManager = defineWidget((ctx) => {
const [toasts, setToasts] = ctx.useState<Toast[]>([]);
let nextId = 1;
const addToast = (message: string, type: Toast["type"], duration = 3000) => {
const toast: Toast = {
id: `toast-${nextId++}`,
message,
type,
duration,
};
setToasts([...toasts, toast]);
};
const dismissToast = (id: string) => {
setToasts(toasts.filter((t) => t.id !== id));
};
const clearAllToasts = () => {
setToasts([]);
};
return ui.layers([
ui.column({ gap: 1, p: 1 }, [
ui.button({
id: "add-toast",
label: "Add Toast",
onPress: () => addToast("New notification", "info"),
}),
ui.button({
id: "clear-toasts",
label: "Clear All",
onPress: clearAllToasts,
disabled: toasts.length === 0,
}),
ui.text(`Active toasts: ${toasts.length}`, { variant: "caption" }),
]),
ui.toastContainer({
toasts,
position: "bottom-right",
maxVisible: 5,
onDismiss: dismissToast,
}),
]);
});
Auto-Dismiss Timer
import { defineWidget } from "@rezi-ui/core";
const AutoDismissExample = defineWidget((ctx) => {
const [toasts, setToasts] = ctx.useState<Toast[]>([]);
const addToast = (message: string, type: Toast["type"]) => {
const toast: Toast = {
id: Date.now().toString(),
message,
type,
duration: 3000,
};
setToasts([...toasts, toast]);
// Auto-dismiss after duration
setTimeout(() => {
setToasts((current) => current.filter((t) => t.id !== toast.id));
}, toast.duration);
};
return ui.layers([
ui.button({
id: "show-toast",
label: "Show Toast",
onPress: () => addToast("Auto-dismiss in 3s", "info"),
}),
ui.toastContainer({
toasts,
position: "bottom-right",
onDismiss: (id) => setToasts(toasts.filter((t) => t.id !== id)),
}),
]);
});
Custom Styling
ui.toastContainer({
toasts,
position: "bottom-right",
frameStyle: {
background: { r: 30, g: 30, b: 40 },
foreground: { r: 220, g: 220, b: 230 },
border: { r: 100, g: 120, b: 150 },
},
onDismiss: dismissToast,
});
Notification Patterns
Save Confirmation
const saveConfirmation: Toast = {
id: "save-confirm",
message: "Changes saved successfully",
type: "success",
duration: 2000,
};
Error with Retry
const errorWithRetry: Toast = {
id: "save-error",
message: "Failed to save changes",
type: "error",
duration: 0,
action: {
label: "Retry",
onAction: () => {
retrySave();
dismissToast("save-error");
},
},
};
Update Available
const updateNotification: Toast = {
id: "update-available",
message: "A new version is available",
type: "info",
duration: 0,
action: {
label: "Update Now",
onAction: () => {
startUpdate();
dismissToast("update-available");
},
},
};
Best Practices
- Keep messages concise - One line of text is ideal for terminal UI
- Use appropriate types - Match type to message severity
- Set sensible durations - 3s for info, 5s for warnings/errors
- Limit visible toasts - Use
maxVisible to prevent clutter
- Provide actions - Add action buttons for actionable notifications
- Stack toasts - Multiple toasts should stack vertically
Accessibility
- Toasts appear in a consistent location
- Auto-dismiss timers provide time to read message
- Action buttons are keyboard accessible
- Toast type is conveyed through color and icon
- Modal - For blocking dialogs that require user action
- Callout - For inline alert messages
- Error Display - For detailed error information
Location in Source
- Types:
packages/core/src/widgets/types.ts:2246-2296
- Factory:
packages/core/src/widgets/ui.ts:1762