VibeWeek
Home/Grow/Toast & Snackbar Notifications: Tell Users What Just Happened (Without Annoying Them)

Toast & Snackbar Notifications: Tell Users What Just Happened (Without Annoying Them)

⬅️ Day 6: Grow Overview

If your SaaS has any actions users take — saving, deleting, sharing, inviting, uploading — toast notifications are how you confirm "that just worked" or "that failed; here's what to do." The naive implementation is wrong in predictable ways: toasts that disappear too fast to read; toasts that linger forever; no way to undo destructive actions; toasts blocking content; missing keyboard accessibility; screen-reader silence. Most indie SaaS ships toast.success("Saved!") and considers it done. The fix is a deliberate notification UX layer with clear hierarchy (info vs success vs warning vs error), durations that match content, undo affordances, and accessibility wired in.

A working toast system answers: when to show toast vs other UI (modal / inline / banner), how long to show (varies by content), where to position (top-right modern; bottom-center mobile), how to handle multiple stacked toasts, how to support undo, how to handle keyboard / screen readers, how to handle errors with action paths, and which library to use (sonner / react-hot-toast / shadcn).

This guide is the implementation playbook for toast UX. Companion to Form Validation UX, Error Handling & Custom Error Pages, Real-time Collaboration, Keyboard Shortcuts & Command Palette, and In-App Notifications.

Why Toasts Matter

Get the failure modes clear first.

Help me understand toast failures.

The 8 categories:

**1. Disappears too fast**
"Saved!" flashes; user wasn't looking; missed.

**2. Stays too long**
"Profile updated!" sticks around; covers content; user dismisses.

**3. Stacks badly**
5 toasts pile up; cover screen; some never read.

**4. Same UX for info / error**
User can't tell what's important; ignores all.

**5. Generic "Error"**
What happened? What to do? User has no path.

**6. No undo for destructive**
"Deleted post" — but I didn't mean to.

**7. Inaccessible**
Screen reader silent; keyboard users can't dismiss.

**8. Mobile broken**
Toast covers nav; can't tap below; UX dies.

For my product:
- Top toast scenarios
- Worst current UX

Output:
1. Top failure modes
2. Volume / frequency
3. Priorities

The biggest unforced error: using toast for everything. Login success → toast. Form save → toast. New message → toast. User drowns in toasts; tunes them out; misses the important one (payment failed). Use toast judiciously.

When to Use Toast vs Other UI

Help me decide when to use toast.

The 5 confirmation patterns:

**1. Toast (transient acknowledgment)**

Use for:
- Action succeeded ("Saved")
- Background action completed ("Export ready")
- Non-critical errors with recovery ("Network error; retrying")
- Information that doesn't block flow ("Connected to Slack")

NOT for:
- Critical errors blocking action (use inline error or modal)
- Multi-step decisions
- Long messages (>2 lines)
- Persistent state communication (use banner)

**2. Inline error / message**

Use for:
- Form field errors ("Email is required")
- Per-component errors ("Couldn't load comments; retry?")
- Status next to specific element

**3. Modal / dialog**

Use for:
- Critical decisions ("Delete account?")
- Multi-step flows
- Required confirmation

**4. Banner (persistent in-page)**

Use for:
- Account-level state ("Trial ends in 3 days")
- System-wide notices ("Scheduled maintenance Sunday")
- Cookie consent

**5. In-app notification center (bell icon)**

Use for:
- Mentions
- Activity from collaborators
- Persistent items requiring attention

**The decision rule**:

- Transient (<5 sec relevant)? → Toast
- Specific to component? → Inline
- Requires decision? → Modal
- Persistent state? → Banner
- Async / actionable later? → Notification center

For my product:
- Audit current usage

Output:
1. Per-action: which UI?
2. Refactor list (wrong UI today)

The discipline: don't use toast for "important" messages. Important = persistent / actionable / blocking. Toast = "by the way, this happened." Mismatch = users miss critical info.

Toast Anatomy & Timing

Help me design toast structure.

The components:

**Title (1 short line)**:
- Action: "Saved" / "Sent" / "Deleted"
- Status: "Updated" / "Connected" / "Disconnected"
- Brief: 1-3 words

**Description (optional, 1 line)**:
- Context: "Your changes are saved"
- Consequence: "We'll email when complete"
- Optional; many toasts don't need

**Icon (optional, semantic)**:
- ✓ Success (green)
- ⚠ Warning (yellow)
- ✕ Error (red)
- ℹ Info (blue)

**Action (optional)**:
- "Undo" button
- "View" button
- "Retry" button

**Close button** (optional but recommended):
- × to dismiss manually

**Duration**:

Short (3-4s): brief acknowledgments
- "Saved"
- "Copied to clipboard"
- "Connected"

Medium (5-7s): more meaningful events
- "Invited 3 team members"
- "Export ready in 2 minutes"
- "Connection failed; retrying"

Long (10-15s) or persistent: requires action
- "Deleted post — Undo?"
- "Failed to save; retry?"
- "Payment declined; update card"

**Auto-dismissable**:

- Success: yes; auto-dismiss after 3-5s
- Warning: yes; 5-7s
- Error: NO auto-dismiss for actionable errors; user must dismiss

**Hover-to-pause**:

User hovers toast → pause auto-dismiss timer. Resume on mouse-leave.

This handles "I was reading and it disappeared" frustration.

For my toasts: [audit]

Output:
1. Per-toast: title / description / action
2. Duration policy
3. Auto-dismiss rules

The discipline: errors stay until dismissed. Auto-dismissing errors = users miss them. Required action toasts (especially payment / save failures) need explicit dismissal.

Position and Stacking

Help me handle position + stacking.

Position:

**Desktop conventions**:
- Top-right: most modern apps (Linear, Notion, Vercel)
- Bottom-right: alternative; less obtrusive
- Top-center: for global / system messages
- Bottom-center: mobile-first (matches mobile)

**Mobile**:
- Bottom-center is dominant (matches iOS / Android system)
- Top-center for system-wide
- Avoid: top-right (cramped on mobile)

**Modal / dialog interactions**:
- Toasts above modals (z-index higher)
- Or: toasts auto-dismiss when modal opens (prevent stacking)

**Stacking**:

Maximum visible: 3-5
Beyond: oldest dismisses; or "+ N more" indicator

Order:
- Newest at top (or bottom; consistent)
- Stack vertically; small gap

```css
.toast-container {
  position: fixed;
  top: 1rem;
  right: 1rem;
  display: flex;
  flex-direction: column-reverse; /* newest on bottom; visual stack from top */
  gap: 0.5rem;
  z-index: 100;
  max-width: 400px;
}

Animations:

  • Enter: slide-in (200ms)
  • Exit: fade-out (200ms)
  • Reduce-motion preference: cross-fade only

Don't:

  • Cover important UI (avoid bottom-center on desktop where action buttons live)
  • Stack >5 toasts (becomes wall of notifications)
  • Position randomly (consistent placement = users learn to look there)

For my UI: [pick]

Output:

  1. Position
  2. Stacking limit
  3. Animations

The mobile detail most miss: **position MUST differ on mobile**. Top-right is fine on desktop; on mobile it's cramped + covers nav. Use bottom-center on mobile; switch via media query.

## Toast Types: Success, Error, Warning, Info

Help me design semantic types.

The 4 standard types:

Success (green):

✓ Saved
[Optional: "Your changes are saved"]
  • Auto-dismiss 3-5s
  • Subtle; doesn't demand attention
  • Use for: completed actions

Error (red):

✕ Couldn't save
"Network error. [Retry]"
  • Don't auto-dismiss (or 10s+)
  • Stands out; demands attention
  • Provide action when applicable
  • Use for: action failed; needs user

Warning (yellow):

⚠ Approaching limit
"Using 90% of plan storage. [Upgrade]"
  • 7-10s or persistent
  • Less alarming than error
  • Action when applicable
  • Use for: caution; not yet failed

Info (blue / neutral):

ℹ Connected to Slack
[Optional: "You'll receive notifications in #general"]
  • 4-5s
  • Neutral / contextual
  • Use for: state changes; FYI

Per-type styling:

.toast-success { background: #10b981; color: white; }
.toast-error { background: #ef4444; color: white; }
.toast-warning { background: #f59e0b; color: white; }
.toast-info { background: #3b82f6; color: white; }

Or subtle: white background with colored border + icon.

Don't rely on color alone (a11y):

Color-blind users:

  • Add icon (✓ / ✕ / ⚠ / ℹ)
  • Or text prefix ("Success: ", "Error: ")

For my types: [audit]

Output:

  1. Per-type design
  2. Default duration
  3. Action templates

The accessibility-must: **icon + color, never color alone**. WCAG 1.4.1 requires non-color indication. Add ✓/✕/⚠/ℹ icons.

## Undo Pattern: The Soft-Delete Lifesaver

Help me implement undo.

The pattern:

User does destructive action → toast appears: "Deleted [item]. [Undo]"

Within 5-10 seconds, click Undo → restores.

After timeout, action commits.

async function deletePost(postId: string) {
  // Optimistically remove from UI
  setPosts(prev => prev.filter(p => p.id !== postId));
  
  // Show toast with undo
  const toastId = toast.success('Post deleted', {
    action: {
      label: 'Undo',
      onClick: () => {
        // Restore
        setPosts(prev => [...prev, deletedPost]);
        clearTimeout(timeout);
      }
    },
    duration: 8000,
  });
  
  // Actually delete after 8s if not undone
  const timeout = setTimeout(async () => {
    await api.deletePost(postId);
  }, 8000);
}

Why this works:

  • User undoes accidents instantly (no support ticket)
  • Server-side delete delayed; can be reverted
  • After timeout: actually deletes
  • Users feel safe; less hesitation to take actions

The pattern variations:

1. Soft-delete with timer (above):

  • 8-30 second window
  • Server commit after timeout

2. Soft-delete with retention:

  • Mark deleted_at NOW()
  • "Trash" / "Recently Deleted" — 30 days to restore
  • Hard delete after 30 days (cron)

3. Both:

  • Toast + undo for immediate
  • Trash for delayed recovery

Not all actions warrant undo:

  • Sending email / message → no undo (already sent)
  • Payment → no undo
  • Account deletion → multi-step modal; not toast-undo

Reserve undo for: destructive but reversible (delete posts / files / archived items).

For my actions:

  • Top destructive operations
  • Undo policy

Output:

  1. Per-action: undo or not
  2. Implementation
  3. Trash / retention if soft-delete

The win: **8-second undo window**. Catches 80%+ of accidental deletes; reduces support tickets; users feel safer.

## Library Choices

Help me pick a toast library.

The 2026 landscape:

sonner (by Emil Kowalski):

npm install sonner

Pros:

  • Modern; minimal API; Vercel-favorite
  • Stack handling correct
  • Animations smooth
  • Tiny bundle (~5KB)
  • Used by Vercel, Linear-flavor
  • Promise toasts (auto-loading / success / error)

Cons:

  • Newer; smaller ecosystem
  • Some advanced customization requires CSS

Recommended for: most React apps in 2026.

import { Toaster, toast } from 'sonner';

function App() {
  return (
    <>
      <Toaster position="top-right" />
      <button onClick={() => toast.success('Saved')}>Save</button>
    </>
  );
}

// Promise toasts:
toast.promise(saveData(), {
  loading: 'Saving...',
  success: 'Saved!',
  error: 'Failed to save',
});

react-hot-toast:

Established alternative. Similar API to sonner.

Pros:

  • Mature; many examples online
  • Tiny bundle
  • Promise toasts

Cons:

  • Slightly more verbose API than sonner

shadcn/ui Toast:

Built on Radix UI primitives.

Pros:

  • Accessible; ARIA-compliant
  • Customizable via Tailwind
  • Part of shadcn ecosystem

Cons:

  • More boilerplate than sonner
  • shadcn replaced "Toast" with Sonner-based "Sonner" component in 2024

shadcn Sonner = wraps sonner with shadcn theming. Recommended.

Native browser Notifications API:

For desktop / OS-level notifications (not in-app).

Different use case: lock-screen notifications when tab inactive.

MUI Snackbar / Mantine Notifications / Chakra Toast:

Component-library bundled. Use if already using these libraries.

For my stack: [pick]

Output:

  1. Library pick
  2. Setup
  3. Migration plan

The 2026 default for React: **sonner** (or shadcn/ui's sonner integration if using shadcn). 5KB; correct stacking; promise toasts; accessibility built-in.

## Accessibility: Make Toasts Speak

Help me handle a11y.

The required behaviors:

1. ARIA live region

Toast container:

<div role="region" aria-live="polite" aria-label="Notifications">
  <!-- toasts inserted here -->
</div>

Modes:

  • aria-live="polite": screen reader announces when idle (default for non-critical)
  • aria-live="assertive": announces immediately (use for errors only)
  • aria-live="off": never announce (avoid)

2. Per-toast role

<div role="status" aria-atomic="true">
  Saved successfully
</div>

<!-- For errors: -->
<div role="alert" aria-atomic="true">
  Couldn't save. [Retry]
</div>

aria-atomic="true" = read full toast as a unit (not just changed parts).

3. Focus management

Don't auto-focus toasts. They're informational; user might be typing. EXCEPT: toasts with required action (e.g. payment-failed with "Update Card" link) — focus the action button.

4. Keyboard dismissal

  • Esc to dismiss focused toast
  • Toast tab-focusable when has actions
  • Tab navigates within toast (action button, close button)

5. Reduced motion

@media (prefers-reduced-motion: reduce) {
  .toast {
    animation: none;
    transition: none;
  }
}

Replace slide animations with cross-fade.

6. Long toasts: read full content

For toasts with description, screen reader reads full text. Don't put critical info in description if you also auto-dismiss in <5s — readers might not finish.

For my toasts: [audit]

Output:

  1. ARIA setup
  2. Live region modes
  3. Test plan

The single most-missed accessibility detail: **role="alert" for errors only**. Using assertive live region for everything = users overwhelmed. Reserve for critical interruptions.

## Common Toast Mistakes

Help me avoid mistakes.

The 10 mistakes:

1. Toast for everything User overwhelmed; tunes out.

2. Auto-dismiss errors User misses critical info.

3. No undo for destructive Accidental deletes = support tickets.

4. Same UX across types User can't tell important from trivial.

5. Generic "Error" No context; no action.

6. Vague success ("Done") What was done? Saved? Sent?

7. Position covers important UI Toast over save button = can't click save.

8. No accessibility (no live region; no role=alert) Screen readers silent.

9. Mobile position desktop-style Top-right cramped; covers nav.

10. Stacking >5 toasts Wall of notifications; nothing readable.

For my system: [risks]

Output:

  1. Top 3 risks
  2. Mitigations
  3. Audit checklist

The single most-painful mistake: **auto-dismissing errors**. Save fails; toast flashes for 3s; user is typing; misses it; resumes typing thinking it saved; data lost. Errors stay until dismissed.

## What Done Looks Like

A working toast system:
- sonner / shadcn-sonner library
- 4 semantic types (success / error / warning / info) with distinct styling
- Errors don't auto-dismiss; success/info do (3-5s)
- Hover-to-pause auto-dismiss
- Top-right desktop; bottom-center mobile (responsive position)
- Max 5 visible; older dismiss
- Undo button for destructive actions (8-30s window)
- ARIA live region (polite default; assertive for errors only)
- role="status" / role="alert" per type
- Dismissable by × and Esc
- Reduced-motion respected
- Mobile-friendly (size + position)
- Used judiciously (not every action)

The proof you got it right: a user does a destructive action; sees clear toast with undo; clicks undo within 8 seconds; restores. Or: payment fails; sees error toast; reads full message; clicks "Update Card". User feels system is communicative + safe.

## See Also

- [Form Validation UX](form-validation-ux-chat.md) — companion frontend UX
- [Error Handling & Custom Error Pages](error-handling-error-pages-chat.md) — companion error UX
- [Real-time Collaboration](real-time-collaboration-chat.md) — collaboration events drive notifications
- [Keyboard Shortcuts & Command Palette](keyboard-shortcuts-command-palette-chat.md) — Esc to dismiss
- [In-App Notifications](in-app-notifications-chat.md) — companion persistent notifications
- [Notification Preferences & Unsubscribe](notification-preferences-unsubscribe-chat.md) — adjacent settings
- [Soft Delete vs Hard Delete](soft-delete-vs-hard-delete-chat.md) — undo via soft-delete
- [Performance Optimization](performance-optimization-chat.md) — toast bundle size impact
- [Dark Mode Implementation](dark-mode-implementation-chat.md) — companion polish
- [VibeReference: React](https://vibereference.dev/frontend/react) — React patterns
- [VibeReference: Shadcn](https://vibereference.dev/frontend/shadcn) — shadcn-sonner integration
- [VibeReference: Accessibility](https://vibereference.dev/product-and-design/accessibility) — broader a11y context