Build a Toast / Notification System
Learn how to build a reusable toast notification system with auto-dismiss, stacking, severity types, and smooth animations.
Table of Contents
Problem Statement
Build a toast notification system that supports:
- Multiple toast types: success, error, warning, info
- Auto-dismiss after a configurable duration
- Manual dismiss via close button
- Stacking multiple toasts simultaneously
- Smooth enter and exit animations
- Pause auto-dismiss on hover (bonus)
Why this question?
Toast systems test your understanding of component lifecycle, timer management, state arrays, animations, and portal rendering. It's a common interview question because it touches many React fundamentals in a compact problem.
Component Architecture
A clean toast system typically has three parts:
ToastPage (or any parent) ├── State: Toast[] array ├── addToast(type, message) → appends to array ├── removeToast(id) → filters from array └── ToastContainer └── Toast (× N) ├── Icon (based on type) ├── Message text ├── Close button └── Auto-dismiss timer (setTimeout)
Interview tip
Interviewers like seeing a clear separation between the toast manager (state + add/remove logic) and the individual toast component (rendering + timer). This shows you think in composable pieces.
Toast State Management
Each toast needs a unique ID, a type, a message, and a duration:
type ToastType = "success" | "error" | "warning" | "info"; interface Toast { id: string; type: ToastType; message: string; duration: number; // ms, default 3000 }
const [toasts, setToasts] = useState<Toast[]>([]); const addToast = (type: ToastType, message: string, duration = 3000) => { const id = crypto.randomUUID(); setToasts(prev => [...prev, { id, type, message, duration }]); }; const removeToast = (id: string) => { setToasts(prev => prev.filter(t => t.id !== id)); };
Auto-Dismiss with Timers
Each toast should start a timer when it mounts and clean it up when it unmounts:
useEffect(() => { const timer = setTimeout(() => { onDismiss(toast.id); }, toast.duration); return () => clearTimeout(timer); }, [toast.id, toast.duration, onDismiss]);
Pause on hover
For the bonus "pause on hover" feature, track remaining time with a ref. On mouseenter, clear the timeout and save the remaining ms. On mouseleave, start a new timeout with the remaining time.
Animations & Transitions
CSS transitions or keyframe animations work well for toast enter/exit. A common pattern:
Enter: translateX(100%) → translateX(0) with opacity 0 → 1 Exit: translateX(0) → translateX(100%) with opacity 1 → 0 Use a state flag like "isExiting" to trigger the exit animation before actually removing from the array (setTimeout for animation duration).
Accessibility
Toasts should be accessible to screen readers:
- Use role="alert" or role="status" on toast elements
- Use aria-live="polite" for info/success, aria-live="assertive" for errors
- Ensure close buttons have aria-label
- Don't rely solely on color to convey toast type — use icons too
Interview Follow-up Questions
Q:How would you implement a global toast system accessible from any component?
A: Use React Context to provide addToast/removeToast functions. Wrap the app in a ToastProvider that manages the state and renders the ToastContainer via a portal.
Q:How do you prevent memory leaks with toast timers?
A: Always clear timeouts in the useEffect cleanup function. If a toast is manually dismissed before the timer fires, the cleanup ensures the stale timeout doesn't try to update unmounted state.
Q:How would you limit the number of visible toasts?
A: Maintain a queue. Only render the first N toasts from the array. When one is dismissed, the next in queue slides in. This prevents the screen from being overwhelmed.
Ready to build it yourself?
We've set up the toast container and trigger buttons. Implement the state management, auto-dismiss timers, and animations from scratch.