Loading…
Loading…
A brief, auto-dismissing message that appears temporarily to provide feedback.
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:
When NOT to use a Toast:
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.
| 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 | 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. |
When multiple toasts fire in quick succession:
| 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 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.
| 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. |
| 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) |
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.
| 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. |
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.| 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 |
prefers-reduced-motion to disable slide animations and use instant opacity transitions instead.For accessible notification patterns, see our ARIA Attributes Guide and WCAG Practical Guide.
prefers-reduced-motion. Replace slide animations with opacity-only transitions for users who've opted out of motion.<!-- 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>
);
}| 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 |
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.