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:
- Toast queue: shared state for active toasts
- Toast viewport: renders the active toasts in a fixed position
- 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
- Trigger function adds to queue
- Viewport renders the new toast with enter animation
- Timer counts down toward auto-dismiss
- Pause timer when user hovers (keep toast visible)
- Resume timer on mouse leave
- 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.