Overview
The Toast (also called a snackbar, notification, or flash message) is a brief, non-intrusive message that appears temporarily to provide feedback about an action or system event. It slides into view, stays for a few seconds, and dismisses itself — no user interaction required.
Toasts occupy a unique position in the feedback hierarchy. Unlike Alerts, which persist inline on the page, toasts are ephemeral. Unlike Dialogs, they don't interrupt the user's workflow. They're the UI equivalent of a quick nod — "Got it, saved" — before getting out of the way.
The key design challenge with toasts is timing and placement. Show them too briefly and users miss the message. Show them too long and they become noise. Place them in the wrong corner and they obscure important content. Stack too many at once and the screen becomes a wall of notifications.
When to use a Toast:
- To confirm a completed action ("Message sent", "Item added to cart", "Settings saved")
- To surface non-critical system events ("New version available", "Connection restored")
- To provide undo opportunities ("Email deleted — Undo")
- For brief error feedback that doesn't require user action ("Failed to save draft — retrying")
When NOT to use a Toast:
- For critical errors that require action — use an inline Alert or Dialog
- For form validation feedback — display errors next to the relevant Text Input fields
- For permanent status information — use a Badge or persistent banner
- For multi-step confirmation flows — use a Dialog or Stepper
Use our Animation Generator to craft smooth entrance/exit animations and the Transition Generator to fine-tune easing curves and durations for toast slide-ins.
Variants
Common Toast Variants
| Variant | Purpose | Visual Treatment |
|---|---|---|
| Success | Confirms a completed action ("Saved successfully") | Green accent, checkmark icon. Most common toast type. |
| Error | Reports a failed action ("Upload failed") | Red accent, error/warning icon. Should include a retry action when possible. |
| Warning | Alerts about potential issues ("Storage almost full") | Yellow/amber accent, warning triangle icon. |
| Info | Provides neutral information ("2 items updated") | Blue accent, info circle icon. Default when no semantic meaning applies. |
| Neutral | Minimal, uncolored notification | No color accent, subtle background. For low-priority messages. |
| With Action | Includes a clickable action ("Undo", "View", "Retry") | Action button (text link style) alongside the message. |
| With Description | Title + secondary description text | Two-line layout for context-rich messages. |
| Promise | Shows loading → success/error based on async result | Spinner while pending, then transitions to success or error. |
Position Variants
| Position | Use Case |
|---|---|
| Bottom-right | Default for most apps. Least likely to obstruct content. |
| Bottom-left | Common alternative, especially in LTR layouts with right-side sidebars. |
| Bottom-center | Mobile-first apps, Material Design convention. |
| Top-right | Notification-heavy apps, dashboard panels. |
| Top-center | High-visibility messages that should catch attention immediately. |
Stacking Behavior
When multiple toasts fire in quick succession:
- Stack vertically (newest on top or bottom — be consistent)
- Limit visible toasts (3–5 max). Queue excess and show sequentially.
- Expand on hover — collapse stacked toasts into a compact pile, expand on hover to reveal all.
Properties
Toast Properties
| Property | Type | Default | Description |
|---|---|---|---|
variant | 'success' | 'error' | 'warning' | 'info' | 'neutral' | 'info' | Visual style and semantic meaning |
title | string | — | Primary toast message (required) |
description | string | — | Optional secondary description text |
duration | number | 5000 | Auto-dismiss time in milliseconds. Set to Infinity for persistent toasts. |
dismissible | boolean | true | Shows a close button |
action | { label: string; onClick: () => void } | — | Optional action button (e.g., "Undo") |
icon | ReactNode | Auto per variant | Custom icon override |
position | 'top-right' | 'top-center' | 'bottom-right' | 'bottom-center' | 'bottom-left' | 'bottom-right' | Screen position of the toast container |
onDismiss | () => void | — | Callback when toast is dismissed (manually or by timeout) |
pauseOnHover | boolean | true | Pauses the auto-dismiss timer on mouse hover |
pauseOnFocusLoss | boolean | true | Pauses auto-dismiss when the browser tab loses focus |
Token Mappings
Design Token Mappings
| Token Category | Token Example | Toast Usage |
|---|---|---|
| Color – Success | --color-success-600, --color-success-50 | Success variant border/icon and background |
| Color – Error | --color-error-600, --color-error-50 | Error variant border/icon and background |
| Color – Warning | --color-warning-600, --color-warning-50 | Warning variant styling |
| Color – Info | --color-info-600, --color-info-50 | Info variant styling |
| Color – Surface | --color-surface-elevated | Toast background (neutral variant) |
| Color – Text | --color-text, --color-text-muted | Title and description text |
| Shadow | --shadow-lg | Elevation to lift toast above page content |
| Border Radius | --radius-lg (12px) | Toast container corners |
| Spacing | --space-3 (12px), --space-4 (16px) | Internal padding, gap between icon/text/action |
| Typography | --font-size-sm, --font-weight-medium | Title and description text styles |
| Transition | --duration-normal (200ms) | Entrance/exit animation. Fine-tune with Transition Generator. |
| Z-index | --z-toast (60) | Stacks above modals and page content |
Use the Animation Generator to create polished slide + fade entrance effects for your toasts.
States
Toast Lifecycle States
| State | Visual Behavior | Duration |
|---|---|---|
| Entering | Slides in from the edge (right, bottom, or top depending on position) with a fade-in. | 200–300ms. Use transform: translateX() + opacity for GPU-accelerated animation. |
| Visible | Fully visible, auto-dismiss timer running. Progress bar optionally shows remaining time. | 3,000–8,000ms depending on content length. |
| Hovered | Timer paused. Slight visual feedback (subtle shadow increase or border highlight). | Indefinite while cursor hovers. |
| Dismissing | Slides out + fades. Remaining toasts reflow to fill the gap smoothly. | 150–200ms. Should feel faster than entering. |
| Queued | Not visible. Waiting for a visible slot to open. | Depends on queue length and dismiss timing. |
Auto-Dismiss Duration Guidelines
| Content | Recommended Duration |
|---|---|
| Short message (1–5 words) | 3,000ms |
| Standard message (5–15 words) | 5,000ms (default) |
| Message with description | 6,000–8,000ms |
| Message with action ("Undo") | 8,000–10,000ms (give users time to act) |
| Error requiring acknowledgment | Infinity (manual dismiss only) |
Progress Indicator
Some toast implementations show a shrinking progress bar at the bottom to indicate remaining time. This is helpful for toasts with actions — users can see how long they have to hit "Undo." Pause the progress bar on hover along with the timer.
Configure entrance/exit animations with our Transition Generator — a cubic-bezier(0.16, 1, 0.3, 1) easing produces a natural slide-in feel.
Accessibility
WCAG Requirements
| Criterion | Level | Requirement |
|---|---|---|
| SC 4.1.3 Status Messages | AA | Toast messages must be announced by screen readers without receiving focus. Use role="status" or aria-live="polite". |
| SC 1.4.3 Contrast (Minimum) | AA | Toast text must have 4.5:1 contrast against its background. Verify with our Contrast Checker. |
| SC 2.2.1 Timing Adjustable | A | Users must be able to extend or dismiss the auto-dismiss timer. Pausing on hover partially satisfies this. |
| SC 2.2.4 Interruptions | AAA | Non-emergency toasts should not interrupt the user's task. Avoid stealing focus. |
| SC 1.4.11 Non-text Contrast | AA | Toast borders and icons must have 3:1 contrast against their background. |
ARIA Implementation
The toast container should use a live region so screen readers announce new toasts without moving focus:
<!-- Toast container (always in the DOM) -->
<div class="toast-region" role="region" aria-label="Notifications">
<ol aria-live="polite" aria-relevant="additions">
<!-- Toasts are injected here -->
<li role="status">
<p class="toast-title">Settings saved</p>
</li>
</ol>
</div>
Key decisions:
role="status"— For informational toasts. Screen readers announce them at the next pause (non-intrusive).role="alert"— For error/warning toasts that need immediate attention. Screen readers interrupt current speech. Use sparingly.aria-live="polite"— On the container. New children are announced without interruption.aria-live="assertive"— Only for critical errors. Interrupts the user's current screen reader output.
Keyboard Interaction
| Key | Action |
|---|---|
| Tab | Moves focus to the toast's action button or close button (if dismissible) |
| Escape | Dismisses the currently focused toast |
| Enter / Space | Activates the action button or close button |
Important Accessibility Considerations
- Don't rely solely on toasts for critical information. Users who are away from the screen, using screen magnifiers, or processing other content may miss them entirely.
- Keep messages concise. Screen readers announce the entire content — long messages are disorienting when spoken aloud.
- Toast actions must be reachable by keyboard. The "Undo" button in a toast must be focusable and operable via Tab + Enter.
- Respect motion preferences. Use
prefers-reduced-motionto disable slide animations and use instant opacity transitions instead.
For accessible notification patterns, see our ARIA Attributes Guide and WCAG Practical Guide.
Usage Guidelines
Do's
- ✅ Keep toast messages to one sentence. "Project saved" is better than "Your project has been successfully saved to the server."
- ✅ Use appropriate variants. Green for success, red for errors. Don't use a success toast for neutral info — color carries meaning.
- ✅ Include an "Undo" action for destructive operations. "Email deleted — Undo" gives users a safety net. It's faster than a confirmation dialog and less disruptive.
- ✅ Pause the timer on hover. Users who are reading the message or reaching for the action button shouldn't have it disappear from under their cursor.
- ✅ Respect
prefers-reduced-motion. Replace slide animations with opacity-only transitions for users who've opted out of motion.
Don'ts
- ❌ Don't use toasts for form validation errors. Show errors inline next to the relevant Text Input. A toast saying "Invalid email" doesn't tell the user which field is wrong.
- ❌ Don't stack more than 3–5 toasts. If actions generate frequent toasts, debounce or batch them ("3 items deleted" not three separate toasts).
- ❌ Don't use toasts for permanent information. If the user needs to reference the information later, put it on the page.
- ❌ Don't make toasts the only error channel. Always log errors somewhere persistent (console, error boundary, notification center) in addition to showing a toast.
- ❌ Don't auto-dismiss error toasts too quickly. Errors need more reading time than confirmations. Use 8,000ms+ or require manual dismissal.
Content Guidelines
- Title: 2–6 words, past tense for confirmations ("Settings saved", "Email sent"), present tense for ongoing ("Uploading file…").
- Description: Optional. One sentence of context. "Your changes will take effect in 5 minutes."
- Action label: Single verb or short phrase — "Undo", "Retry", "View", "Dismiss".
- Avoid technical jargon: "Network error" is fine. "ECONNREFUSED 503 timeout" is not.
Code Snippets
<!-- Toast container — always in the DOM -->
<div class="toast-region" role="region" aria-label="Notifications">
<ol class="toast-list" aria-live="polite" aria-relevant="additions">
<!-- Success toast -->
<li class="toast toast-success" role="status">
<svg class="toast-icon" aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M10 18a8 8 0 1 0 0-16 8 8 0 0 0 0 16Zm3.857-9.809a.75.75 0 0 0-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 1 0-1.06 1.061l2.5 2.5a.75.75 0 0 0 1.137-.089l4-5.5Z" clip-rule="evenodd"/>
</svg>
<div class="toast-content">
<p class="toast-title">Settings saved</p>
<p class="toast-description">Your preferences have been updated.</p>
</div>
<button type="button" class="toast-close" aria-label="Dismiss notification">
<svg aria-hidden="true" width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
<path d="M5.28 4.22a.75.75 0 0 0-1.06 1.06L7.94 8l-3.72 3.72a.75.75 0 1 0 1.06 1.06L9 9.06l3.72 3.72a.75.75 0 1 0 1.06-1.06L10.06 8l3.72-3.72a.75.75 0 0 0-1.06-1.06L9 6.94 5.28 4.22Z"/>
</svg>
</button>
</li>
<!-- Error toast with action -->
<li class="toast toast-error" role="alert">
<svg class="toast-icon" aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M10 18a8 8 0 1 0 0-16 8 8 0 0 0 0 16ZM8.28 7.22a.75.75 0 0 0-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 1 0 1.06 1.06L10 11.06l1.72 1.72a.75.75 0 1 0 1.06-1.06L11.06 10l1.72-1.72a.75.75 0 0 0-1.06-1.06L10 8.94 8.28 7.22Z" clip-rule="evenodd"/>
</svg>
<div class="toast-content">
<p class="toast-title">Upload failed</p>
<p class="toast-description">The file could not be uploaded. Please try again.</p>
</div>
<button type="button" class="toast-action">Retry</button>
<button type="button" class="toast-close" aria-label="Dismiss notification">✕</button>
</li>
</ol>
</div>import { createContext, useContext, useState, useCallback, useId, type ReactNode } from "react";
interface Toast {
id: string;
variant: "success" | "error" | "warning" | "info";
title: string;
description?: string;
action?: { label: string; onClick: () => void };
duration?: number;
}
interface ToastContextValue {
toast: (t: Omit<Toast, "id">) => void;
dismiss: (id: string) => void;
}
const ToastContext = createContext<ToastContextValue | null>(null);
export function useToast() {
const ctx = useContext(ToastContext);
if (!ctx) throw new Error("useToast must be used within ToastProvider");
return ctx;
}
export function ToastProvider({ children }: { children: ReactNode }) {
const [toasts, setToasts] = useState<Toast[]>([]);
const dismiss = useCallback((id: string) => {
setToasts((prev) => prev.filter((t) => t.id !== id));
}, []);
const addToast = useCallback((t: Omit<Toast, "id">) => {
const id = crypto.randomUUID();
setToasts((prev) => [...prev, { ...t, id }]);
const duration = t.duration ?? 5000;
if (duration !== Infinity) {
setTimeout(() => dismiss(id), duration);
}
}, [dismiss]);
return (
<ToastContext.Provider value={{ toast: addToast, dismiss }}>
{children}
<div className="toast-region" role="region" aria-label="Notifications">
<ol aria-live="polite" aria-relevant="additions">
{toasts.map((t) => (
<li key={t.id} className={`toast toast-${t.variant}`} role="status">
<div className="toast-content">
<p className="toast-title">{t.title}</p>
{t.description && <p className="toast-desc">{t.description}</p>}
</div>
{t.action && (
<button type="button" className="toast-action" onClick={t.action.onClick}>
{t.action.label}
</button>
)}
<button
type="button"
className="toast-close"
aria-label="Dismiss"
onClick={() => dismiss(t.id)}
>
✕
</button>
</li>
))}
</ol>
</div>
</ToastContext.Provider>
);
}
// Usage
function SaveButton() {
const { toast } = useToast();
return (
<button
onClick={() =>
toast({
variant: "success",
title: "Settings saved",
description: "Your preferences have been updated.",
})
}
>
Save
</button>
);
}Design Systems
Cross-System Comparison
| Feature | Material 3 | Shadcn/ui | Radix | Ant Design |
|---|---|---|---|---|
| Component | Snackbar | Toast (Radix-based) + Sonner | Toast primitive | message / notification |
| API style | Imperative (enqueueSnackbar) | Hook (useToast) or Sonner's toast() | Declarative (controlled open/close) | Static methods (message.success()) |
| Positioning | Bottom-center (fixed) | Configurable (Sonner: bottom-right default) | BYO positioning | Top-center (message) / top-right (notification) |
| Stacking | Single snackbar, queue-based | Stack with expand-on-hover (Sonner) | Manual | Stack (notification), replace (message) |
| Action button | Single action ("Undo") | Customizable via render | BYO | Duration auto-configures with action |
| Swipe dismiss | Built-in (mobile) | Sonner supports swipe | Not built-in | Not built-in |
| Promise toast | Not built-in | Sonner: toast.promise() | Not built-in | message.loading() → update |
| Variants | No semantic variants | success/error/warning/info + custom | Unstyled primitive | success/error/warning/info/loading |
Notable Approaches
Sonner has emerged as the de facto toast library in the React ecosystem (2024–2026). Created by Emil Kowalski, it handles stacking with an elegant "expand on hover" pattern, supports promise-based toasts (toast.promise(fetch(…))), and includes swipe-to-dismiss for mobile. Shadcn/ui adopted Sonner as its recommended toast solution, replacing the earlier Radix-based approach.
Material 3 Snackbar is intentionally limited: one snackbar at a time, bottom-center positioning, one optional action. This constraint is a design decision — multiple simultaneous snackbars create noise. If you need multiple notifications, Material recommends a separate notification center.
Ant Design separates toasts into two components: message (compact, top-center, for simple confirmations) and notification (richer, top-right, with title + description + icon). This separation is practical — different feedback needs deserve different treatments.
Radix Toast is deliberately low-level. It handles the accessibility layer (live regions, focus management, swipe gestures) but provides no styling, positioning, or stacking logic. It's a primitive — build your own toast system on top of it, or use Sonner which does this for you.
A key trend: Promise-based toasts that show loading → success/error states have become standard. Instead of managing three separate toasts, you call toast.promise(saveData(), { loading: "Saving…", success: "Saved!", error: "Failed" }) and the toast transitions automatically.