Skip to content

Toast

A brief, auto-dismissing message that appears temporarily to provide feedback.

  • Snackbar
  • Notification
  • Flash Message

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

VariantPurposeVisual Treatment
SuccessConfirms a completed action ("Saved successfully")Green accent, checkmark icon. Most common toast type.
ErrorReports a failed action ("Upload failed")Red accent, error/warning icon. Should include a retry action when possible.
WarningAlerts about potential issues ("Storage almost full")Yellow/amber accent, warning triangle icon.
InfoProvides neutral information ("2 items updated")Blue accent, info circle icon. Default when no semantic meaning applies.
NeutralMinimal, uncolored notificationNo color accent, subtle background. For low-priority messages.
With ActionIncludes a clickable action ("Undo", "View", "Retry")Action button (text link style) alongside the message.
With DescriptionTitle + secondary description textTwo-line layout for context-rich messages.
PromiseShows loading → success/error based on async resultSpinner while pending, then transitions to success or error.

Position Variants

PositionUse Case
Bottom-rightDefault for most apps. Least likely to obstruct content.
Bottom-leftCommon alternative, especially in LTR layouts with right-side sidebars.
Bottom-centerMobile-first apps, Material Design convention.
Top-rightNotification-heavy apps, dashboard panels.
Top-centerHigh-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

PropertyTypeDefaultDescription
variant'success' | 'error' | 'warning' | 'info' | 'neutral''info'Visual style and semantic meaning
titlestring—Primary toast message (required)
descriptionstring—Optional secondary description text
durationnumber5000Auto-dismiss time in milliseconds. Set to Infinity for persistent toasts.
dismissiblebooleantrueShows a close button
action{ label: string; onClick: () => void }—Optional action button (e.g., "Undo")
iconReactNodeAuto per variantCustom 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)
pauseOnHoverbooleantruePauses the auto-dismiss timer on mouse hover
pauseOnFocusLossbooleantruePauses auto-dismiss when the browser tab loses focus

Token Mappings

Design Token Mappings

Token CategoryToken ExampleToast Usage
Color – Success--color-success-600, --color-success-50Success variant border/icon and background
Color – Error--color-error-600, --color-error-50Error variant border/icon and background
Color – Warning--color-warning-600, --color-warning-50Warning variant styling
Color – Info--color-info-600, --color-info-50Info variant styling
Color – Surface--color-surface-elevatedToast background (neutral variant)
Color – Text--color-text, --color-text-mutedTitle and description text
Shadow--shadow-lgElevation 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-mediumTitle 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

StateVisual BehaviorDuration
EnteringSlides 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.
VisibleFully visible, auto-dismiss timer running. Progress bar optionally shows remaining time.3,000–8,000ms depending on content length.
HoveredTimer paused. Slight visual feedback (subtle shadow increase or border highlight).Indefinite while cursor hovers.
DismissingSlides out + fades. Remaining toasts reflow to fill the gap smoothly.150–200ms. Should feel faster than entering.
QueuedNot visible. Waiting for a visible slot to open.Depends on queue length and dismiss timing.

Auto-Dismiss Duration Guidelines

ContentRecommended Duration
Short message (1–5 words)3,000ms
Standard message (5–15 words)5,000ms (default)
Message with description6,000–8,000ms
Message with action ("Undo")8,000–10,000ms (give users time to act)
Error requiring acknowledgmentInfinity (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

CriterionLevelRequirement
SC 4.1.3 Status MessagesAAToast messages must be announced by screen readers without receiving focus. Use role="status" or aria-live="polite".
SC 1.4.3 Contrast (Minimum)AAToast text must have 4.5:1 contrast against its background. Verify with our Contrast Checker.
SC 2.2.1 Timing AdjustableAUsers must be able to extend or dismiss the auto-dismiss timer. Pausing on hover partially satisfies this.
SC 2.2.4 InterruptionsAAANon-emergency toasts should not interrupt the user's task. Avoid stealing focus.
SC 1.4.11 Non-text ContrastAAToast 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

KeyAction
TabMoves focus to the toast's action button or close button (if dismissible)
EscapeDismisses the currently focused toast
Enter / SpaceActivates the action button or close button

Important Accessibility Considerations

  1. 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.
  2. Keep messages concise. Screen readers announce the entire content — long messages are disorienting when spoken aloud.
  3. Toast actions must be reachable by keyboard. The "Undo" button in a toast must be focusable and operable via Tab + Enter.
  4. Respect motion preferences. Use 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.

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

html
<!-- 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>
tsx
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

FeatureMaterial 3Shadcn/uiRadixAnt Design
ComponentSnackbarToast (Radix-based) + SonnerToast primitivemessage / notification
API styleImperative (enqueueSnackbar)Hook (useToast) or Sonner's toast()Declarative (controlled open/close)Static methods (message.success())
PositioningBottom-center (fixed)Configurable (Sonner: bottom-right default)BYO positioningTop-center (message) / top-right (notification)
StackingSingle snackbar, queue-basedStack with expand-on-hover (Sonner)ManualStack (notification), replace (message)
Action buttonSingle action ("Undo")Customizable via renderBYODuration auto-configures with action
Swipe dismissBuilt-in (mobile)Sonner supports swipeNot built-inNot built-in
Promise toastNot built-inSonner: toast.promise()Not built-inmessage.loading() → update
VariantsNo semantic variantssuccess/error/warning/info + customUnstyled primitivesuccess/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.

FeedbackSnackbarNotificationFlash Message