Build a Notification Toast System: Queue, Stack, and Animations

Toast notifications appear in nearly every web app. They look trivial; production-quality toasts are not. The interview tests whether you understand queue management, stacking, animations, and the accessibility considerations of dynamic announcements.

Functional requirements

  • Show a toast on user action (success, error, warning, info)
  • Auto-dismiss after a delay
  • Manual dismiss via close button
  • Stack multiple toasts
  • Animate in and out
  • Screen-reader accessible

Architecture

Three components:

  1. Toast queue: shared state for active toasts
  2. Toast viewport: renders the active toasts in a fixed position
  3. Trigger function: imperative API (toast.success(“Saved”))

State management

Common pattern: a global state for toasts. Library options:

  • Zustand store
  • React Context
  • Sonner (popular toast library) — uses its own store

Each toast: { id, type, message, action?, duration }

Toast lifecycle

  1. Trigger function adds to queue
  2. Viewport renders the new toast with enter animation
  3. Timer counts down toward auto-dismiss
  4. Pause timer when user hovers (keep toast visible)
  5. Resume timer on mouse leave
  6. On dismiss: exit animation, then remove from queue

Stacking

When multiple toasts are visible:

  • Newest at top (or bottom)
  • Earlier toasts shift to make room
  • Cap at 3–5 visible (newer pushes oldest out)

Modern UX: stacked-card visual with slight fan effect (Sonner-style).

Position

Common positions:

  • Top right (desktop default)
  • Bottom right (alternate desktop)
  • Bottom center (mobile-friendly)
  • Top center (announcements that need attention)

Make configurable. Don’t cover important content.

Animations

Slide + fade in/out is the standard:

  • Enter: slide from off-screen edge (200ms)
  • Exit: fade out (150ms)
  • Reposition when other toasts dismiss

Animate transform and opacity (compositor-friendly).

Accessibility

  • Use role="status" for non-urgent toasts (announces politely)
  • Use role="alert" for errors (interrupts screen reader)
  • aria-live region appropriate to type
  • Close button is keyboard-accessible
  • Focus stays where it was; don’t hijack focus to the toast

Pause on hover

Useful behavior: timer pauses while user hovers. Implementation:

  • Track elapsed time
  • onMouseEnter pauses timer
  • onMouseLeave resumes from elapsed time

Action buttons

Toasts can have actions (“Undo,” “Retry”):

  • Button inside the toast
  • Click triggers callback
  • Toast dismisses after action

Promise-based toasts

Modern pattern (Sonner):

toast.promise(saveAsync(), {
  loading: "Saving...",
  success: "Saved",
  error: "Failed to save"
});

Single call updates the toast through loading → success/error.

Common antipatterns

  • Toasts that dismiss too fast (errors should stay until dismissed)
  • Toasts that block important UI
  • Animations that ignore prefers-reduced-motion
  • Spam: 10 toasts at once
  • Confirmation toasts that feel like “we did your action” without need

Library options

  • Sonner: the modern standard. Beautiful, accessible, ergonomic API.
  • react-hot-toast: popular, minimal, easy to customize.
  • react-toastify: older, more features.

Frequently Asked Questions

How long should a toast last?

Success: 3–4 seconds. Errors: 5–8 seconds (or persistent). Manual dismiss always available.

Should errors auto-dismiss?

Generally no. Users may miss the message. Either keep visible until dismissed or use longer duration.

How do I prevent duplicate toasts?

Pass an optional ID. If a toast with that ID exists, update it instead of adding new.

Scroll to Top