Skip to main content

Comprehensive Toast Notification System Design

Detailed low-level design for a scalable, accessible toast notification system with queue management and customizable behavior patterns

A robust toast notification system requires careful consideration of display priorities, user interaction patterns, and accessibility compliance while maintaining optimal performance and developer experience through clean component architecture and flexible configuration options.

1. Problem Statement & Requirements

Clarifying questions

  1. What target browsers/devices are we supporting? (e.g., mobile-first, desktop-only, IE11 support)
  2. Do we need to support real-time notifications from server push or are they triggered by user actions?
  3. Should toast notifications be dismissible by user interaction or auto-dismiss after a timeout?
  4. Do we need to support offline scenarios where notifications might queue until connection is restored?
  5. What accessibility requirements are there? (e.g., screen reader support, keyboard navigation)
  6. Do we need to support internationalization for toast messages?
  7. Are there existing design system components we should integrate with?

Functional requirements

  • Display toast notifications with different severity levels (success, error, warning, info)
  • Support custom message content and optional icon
  • Auto-dismiss after configurable timeout (default 5 seconds)
  • Manual dismiss capability via close button or keyboard
  • Queue management for multiple concurrent toasts
  • Support for rich content (HTML, links, markdown)
  • Ability to pause toast queue when user is interacting with page
  • Configurable position (top-right, top-left, bottom-right, bottom-left)
  • Support for stacking multiple toasts simultaneously

Non-functional requirements

  • Performance: Toasts must not cause layout shifts or jank
  • Accessibility: All toasts must be keyboard navigable and screen reader accessible
  • Scalability: System should handle 100+ concurrent toasts without performance degradation
  • Memory efficiency: Toasts should be properly cleaned up from memory after dismissal
  • Resilience: System should not crash or break when toast configuration is malformed

Constraints and assumptions

  • Toasts will be implemented as React components using React Portal
  • Toast system will be global and available throughout the application lifecycle
  • Toasts will not persist across page reloads (client-side only)
  • We assume a modern browser environment with ES6+ support
  • Toasts will be implemented as a singleton pattern to ensure global consistency

Out of scope

  • Server-side push notifications (web sockets, FCM)
  • Persistent storage of toast history or logs
  • Custom animations beyond basic fade transitions
  • Drag-and-drop reordering of toasts
  • Toast grouping or aggregation features
  • Integration with third-party notification services (e.g., Slack, email)
  • Mobile-specific touch gestures for toast dismissal

2. Component Architecture

graph TD
    A[ToastContainer] --> B[Toast]
    A --> C[ToastProvider]
    B --> D[ToastContent]
    B --> E[ToastActions]
    C --> F[useToastHook]
    F --> G[ToastStore]

Component Tree and Responsibilities

The toast system is composed of a container component that manages the toast queue, individual toast components that render notifications, and a provider that exposes the toast API to the application.

Smart Components:

  • ToastContainer - Container that manages toast queue and renders individual toasts
  • ToastProvider - Context provider that exposes toast API to the app
  • useToastHook - Custom hook that provides toast functionality to consumers

Dumb Components:

  • Toast - Individual toast presentation component
  • ToastContent - Content rendering for toast messages
  • ToastActions - Action buttons (close, retry, etc.)

TypeScript Props Contracts

// ToastProvider.tsx
interface ToastProviderProps {
  children: React.ReactNode;
  maxToasts?: number;
  toastContainerId?: string;
}

// Toast.tsx
interface ToastProps {
  id: string;
  type: 'success' | 'error' | 'warning' | 'info';
  title?: string;
  description?: string;
  duration?: number;
  closable?: boolean;
  onClose?: () => void;
  onAction?: () => void;
  actionText?: string;
  className?: string;
}

// ToastContent.tsx
interface ToastContentProps {
  type: 'success' | 'error' | 'warning' | 'info';
  title?: string;
  description?: string;
}

// ToastActions.tsx
interface ToastActionsProps {
  onAction?: () => void;
  onClose?: () => void;
  actionText?: string;
  closable?: boolean;
}

UI State Machine

stateDiagram-v2
    [*] --> Idle
    Idle --> Showing : show()
    Showing --> Dismissing : autoClose | close()
    Dismissing --> Hidden : animationEnd
    Showing --> Idle : timeout
    Hidden --> [*]

Component Responsibilities

  • ToastContainer: Manages toast queue, handles positioning and animations, provides context for toasts
  • ToastProvider: Wraps app to provide toast API via React Context, manages global state
  • useToastHook: Returns toast methods (show, dismiss) and exposes toast queue to consumers
  • Toast: Individual toast presentation with animation lifecycle management
  • ToastContent: Renders content based on type and provided props
  • ToastActions: Handles user interactions with toast (close, action button)

Reusability Decisions

The toast system is designed to be highly reusable across the application. It uses React Context for global access, supports multiple toast types, and provides extensible props for customization. The component tree is designed to be modular with clear separation of concerns.

Component Lifecycle Considerations

Toasts have a lifecycle that includes:

  1. Mounting: Toast is added to queue and rendered
  2. Showing: Toast animates in and displays content
  3. Active: Toast is visible to user (with optional auto-close)
  4. Dismissing: Toast animates out
  5. Unmounting: Toast is removed from DOM

The system handles:

  • Concurrent toasts with unique IDs
  • Auto-dismissal based on duration
  • Manual dismissal via close button
  • Animation state management for smooth transitions
sequenceDiagram
    participant App
    participant ToastProvider
    participant ToastContainer
    participant Toast
    
    App->>ToastProvider: Wrap with ToastProvider
    ToastProvider->>ToastContainer: Render container
    App->>useToastHook: Call toast.show()
    useToastHook->>ToastContainer: Add toast to queue
    ToastContainer->>Toast: Render new toast
    Toast->>Toast: Animate in
    Toast->>Toast: Auto-dismiss after duration
    Toast->>ToastContainer: Remove from queue
    ToastContainer->>Toast: Animate out and remove

3. State Management Design

Local state vs. global state decisions

For a toast/notification system, we need to distinguish between:

  • Local state: Per-toast metadata (visibility, timeout handlers)
  • Global state: Toast queue management, configuration settings, and system-wide toast behavior

The toast queue and configuration should be global since they affect all toasts in the system. Individual toast metadata is local to each toast component instance.

State management approach

I'll use Zustand as the state management solution because:

  1. It provides a clean, minimal API similar to React's useState but with global scope
  2. No provider wrapping needed - simple hooks for consumption
  3. Excellent TypeScript support with type inference
  4. Good performance characteristics for this use case
  5. Easy to test and mock in isolation

Zustand is more appropriate than Redux Toolkit or Context API for this specific use case because:

  • We don't need complex middleware or extensive dev tools
  • Context API would require a provider wrapper and multiple consumers
  • Redux Toolkit introduces unnecessary boilerplate for simple state management

State shape and normalization strategy

// Toast state structure
interface Toast {
  id: string;
  message: string;
  type: 'success' | 'error' | 'warning' | 'info';
  duration?: number;
  dismissible?: boolean;
  action?: {
    label: string;
    onClick: () => void;
  };
  createdAt: number;
}

interface ToastState {
  toasts: Toast[];
  config: {
    position: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
    maxToasts?: number;
    duration?: number;
  };
}

Server state vs. client state separation

In a toast system, all state is client-side:

  • Toast queue and configuration are purely client-side concerns
  • No server-side state management is required for toast functionality
  • Toasts are ephemeral and don't persist across sessions

Cache invalidation strategy

Since toasts are ephemeral and have built-in timeouts, there's no traditional cache invalidation needed. The system manages its own lifecycle through:

  • Automatic removal after duration timeout
  • Manual dismissal by user
  • Position-based limits (maxToasts)

Optimistic updates design

The toast system supports optimistic updates through:

  1. Immediate toast display upon API request
  2. Toast update on success/failure response
  3. User can dismiss immediately if desired

Critical-path hook implementation

// toastStore.ts
import { create } from 'zustand';
import { v4 as uuidv4 } from 'uuid';

interface Toast {
  id: string;
  message: string;
  type: 'success' | 'error' | 'warning' | 'info';
  duration?: number;
  dismissible?: boolean;
  action?: {
    label: string;
    onClick: () => void;
  };
  createdAt: number;
}

interface ToastConfig {
  position: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
  maxToasts?: number;
  duration?: number;
}

interface ToastStore {
  toasts: Toast[];
  config: ToastConfig;
  addToast: (toast: Omit<Toast, 'id' | 'createdAt'>) => string;
  removeToast: (id: string) => void;
  updateToast: (id: string, updates: Partial<Toast>) => void;
  clearToasts: () => void;
  setConfig: (config: Partial<ToastConfig>) => void;
}

const useToastStore = create<ToastStore>((set, get) => ({
  toasts: [],
  config: {
    position: 'top-right',
    maxToasts: 5,
    duration: 5000,
  },
  
  addToast: (toast) => {
    const id = uuidv4();
    const { config, toasts } = get();
    
    // Apply default values
    const toastWithDefaults: Toast = {
      id,
      message: toast.message,
      type: toast.type || 'info',
      duration: toast.duration || config.duration,
      dismissible: toast.dismissible ?? true,
      action: toast.action,
      createdAt: Date.now(),
    };
    
    // Apply maxToasts limit
    const newToasts = [toastWithDefaults, ...toasts].slice(0, config.maxToasts);
    
    set({ toasts: newToasts });
    
    // Auto-remove after duration
    if (toastWithDefaults.duration !== Infinity) {
      setTimeout(() => {
        get().removeToast(id);
      }, toastWithDefaults.duration);
    }
    
    return id;
  },
  
  removeToast: (id) => {
    set((state) => ({
      toasts: state.toasts.filter(toast => toast.id !== id),
    }));
  },
  
  updateToast: (id, updates) => {
    set((state) => ({
      toasts: state.toasts.map(toast => 
        toast.id === id ? { ...toast, ...updates } : toast
      ),
    }));
  },
  
  clearToasts: () => {
    set({ toasts: [] });
  },
  
  setConfig: (newConfig) => {
    set((state) => ({
      config: { ...state.config, ...newConfig },
    }));
  },
}));

// Custom hook for toast consumption
export const useToast = () => {
  const { toasts, config, addToast, removeToast, updateToast, clearToasts } = useToastStore();
  
  const toast = (toast: Omit<Toast, 'id' | 'createdAt'>) => {
    return addToast(toast);
  };
  
  return {
    toasts,
    config,
    toast,
    removeToast,
    updateToast,
    clearToasts,
    setConfig: useToastStore.getState().setConfig,
  };
};

// Toast context provider (if needed for SSR or multiple instances)
export const ToastProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  return (
    <div>
      {children}
      <ToastContainer />
    </div>
  );
};

// Toast container component that renders toasts
const ToastContainer: React.FC = () => {
  const { toasts, config } = useToastStore();
  
  return (
    <div 
      className={`toast-container toast-${config.position}`}
      aria-live="polite"
      aria-atomic="true"
    >
      {toasts.map((toast) => (
        <Toast key={toast.id} toast={toast} />
      ))}
    </div>
  );
};

// Toast component
interface ToastProps {
  toast: Toast;
}

const Toast: React.FC<ToastProps> = ({ toast }) => {
  const [isVisible, setIsVisible] = useState(true);
  const [isHovered, setIsHovered] = useState(false);
  
  const removeToast = useToastStore.getState().removeToast;
  
  useEffect(() => {
    if (toast.duration !== Infinity && !isHovered) {
      const timer = setTimeout(() => {
        setIsVisible(false);
        setTimeout(() => removeToast(toast.id), 300); // Allow animation time
      }, toast.duration);
      
      return () => clearTimeout(timer);
    }
  }, [toast.duration, isHovered]);
  
  const handleDismiss = () => {
    setIsVisible(false);
    setTimeout(() => removeToast(toast.id), 300);
  };
  
  return (
    <div 
      className={`toast toast-${toast.type} ${isVisible ? 'toast-visible' : 'toast-hidden'}`}
      role="alert"
      aria-live="assertive"
      onMouseEnter={() => setIsHovered(true)}
      onMouseLeave={() => setIsHovered(false)}
    >
      <div className="toast-content">
        {toast.message}
        {toast.action && (
          <button 
            className="toast-action"
            onClick={toast.action.onClick}
          >
            {toast.action.label}
          </button>
        )}
      </div>
      {toast.dismissible && (
        <button 
          className="toast-dismiss"
          onClick={handleDismiss}
          aria-label="Close toast"
        >
          ✕
        </button>
      )}
    </div>
  );
};

The critical-path hook useToast provides:

  1. Toast creation with automatic ID generation and timeout handling
  2. State management for toast queue and configuration
  3. Lifecycle control through remove/update operations
  4. Configurable behavior for positioning, max count, and durations

The hook returns a clean API that's easy to consume in any component while maintaining proper state management and lifecycle handling.

4. Routing & Navigation

Route Structure and Hierarchy

The toast/notification system is a global UI feature that doesn't require dedicated routes but needs to be accessible from any part of the application. The system operates as a global singleton component that renders notifications in a fixed position on the screen, typically in the top-right corner.

/
  └── [Global Toast Container] (no route needed)

Code Splitting Strategy

Since the toast system is a global UI component with no route-specific code, there's no need for route-based code splitting. The toast container and its associated logic will be imported directly into the root application component.

Justification:

  • Toasts are rendered globally and don't depend on route-specific bundles
  • The system is lightweight and should be available immediately when the app loads
  • No performance benefit from splitting toast-related code

The toast system itself doesn't participate in navigation guards or authentication flows. However, it should respect authentication state:

  • Toasts may be shown to authenticated users only (e.g., success messages after login)
  • Error notifications should not reveal sensitive authentication information
  • Toasts should be cleared when users log out or navigate away from protected routes

Deep Linking Support and URL Parameter Design

The toast system does not require deep linking support or URL parameter handling. Toasts are ephemeral notifications that don't need to be part of the URL structure.

Browser History Management and Scroll Restoration

The toast system does not interfere with browser history or scroll restoration. Toasts are rendered as overlays that don't affect the DOM structure or scroll position of the underlying content.

// Example toast container integration in app root
import { ToastContainer } from './components/Toast/ToastContainer';

function App() {
  return (
    <div className="app-root">
      {/* Main application content */}
      <main>
        {children}
      </main>
      
      {/* Global toast container - renders at fixed position */}
      <ToastContainer />
    </div>
  );
}

The toast system operates as a singleton component that's always present in the DOM, positioned absolutely or fixed to ensure it appears above all other content regardless of application navigation state.

5. API Integration Layer

API client design pattern

We'll implement a centralized Axios instance with custom configuration for consistent API interactions:

// api/client.ts
import axios, { AxiosInstance } from 'axios';

const apiClient: AxiosInstance = axios.create({
  baseURL: process.env.NEXT_PUBLIC_API_BASE_URL || 'https://api.example.com',
  timeout: 10000,
  headers: {
    'Content-Type': 'application/json',
  },
});

// Request interceptor for auth token injection
apiClient.interceptors.request.use(
  (config) => {
    const token = localStorage.getItem('authToken');
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
  },
  (error) => Promise.reject(error)
);

// Response interceptor for error handling and normalization
apiClient.interceptors.response.use(
  (response) => response,
  (error) => {
    // Handle different error types
    if (error.response?.status === 401) {
      // Handle unauthorized access - redirect to login or trigger auth flow
      localStorage.removeItem('authToken');
      window.location.href = '/login';
    }
    
    // Normalize error response for toast handling
    const normalizedError = {
      status: error.response?.status,
      message: error.response?.data?.message || error.message,
      code: error.response?.data?.code,
    };
    
    return Promise.reject(normalizedError);
  }
);

export default apiClient;

Request/response interceptors

The interceptors handle authentication token injection and standardized error response normalization. They provide centralized error handling that allows the toast system to present consistent user feedback regardless of API error type.

Error handling and retry strategy

For toast integration, we implement a retry strategy with exponential backoff:

// api/retry.ts
export const retryWithBackoff = async (
  fn: () => Promise<any>,
  retries = 3,
  delay = 1000
): Promise<any> => {
  try {
    return await fn();
  } catch (error) {
    if (retries > 0 && error.status !== 401) {
      await new Promise(resolve => setTimeout(resolve, delay));
      return retryWithBackoff(fn, retries - 1, delay * 2);
    }
    throw error;
  }
};

Loading, error, and empty states design

Toast system handles these states through dedicated toast types:

// toast/types.ts
export type ToastType = 'success' | 'error' | 'warning' | 'info' | 'loading';

export interface Toast {
  id: string;
  type: ToastType;
  message: string;
  title?: string;
  duration?: number;
  actions?: ToastAction[];
  dismissible?: boolean;
}

export interface ToastAction {
  label: string;
  onClick: () => void;
}

Data fetching strategy

For toast notifications, we use client-side rendering (CSR) with React Query for optimal caching and revalidation:

// hooks/useToastNotifications.ts
import { useQuery } from '@tanstack/react-query';
import apiClient from '../api/client';

export const useToastNotifications = (userId: string) => {
  return useQuery({
    queryKey: ['user-notifications', userId],
    queryFn: () => apiClient.get(`/users/${userId}/notifications`).then(res => res.data),
    staleTime: 5 * 60 * 1000, // 5 minutes
    cacheTime: 10 * 60 * 1000, // 10 minutes
  });
};

API response transformation layer

We implement a DTO-to-domain model mapping for consistent toast data:

// api/transformers.ts
import { Toast } from '../toast/types';

export const transformNotificationToToast = (notification: any): Toast => {
  return {
    id: notification.id,
    type: notification.type === 'ERROR' ? 'error' : 'info',
    message: notification.message,
    title: notification.title,
    duration: notification.duration || 5000,
    dismissible: notification.dismissible !== false,
  };
};

Sequence diagram for primary data flows

sequenceDiagram
    participant U as User
    participant T as ToastManager
    participant A as API Client
    participant S as Server

    U->>T: Trigger notification (e.g., form submission)
    T->>A: Make API request
    A->>S: Send request
    S-->>A: Response (success/error)
    A-->>T: API response with error details
    T->>T: Transform to Toast format
    T->>U: Display toast notification

6. Performance Design

Core Web Vitals Targets

  • Largest Contentful Paint (LCP): <2.5s for toast notifications
  • Interaction to Next Paint (INP): <200ms for dismiss interactions
  • Cumulative Layout Shift (CLS): <0.1 for toast container layout stability

Bundle Size Budget per Route

Toast system should contribute <5KB to total bundle size when minified and gzipped. This includes:

  • Toast component implementation (~2KB)
  • State management overhead (~1KB)
  • Animation utilities (~1.5KB)
  • Accessibility helpers (~0.5KB)

Critical Rendering Path Optimization

Toasts must not block rendering of critical content:

  • Toast components are rendered in a separate DOM node (portal) outside the main render tree
  • CSS is preloaded with preload or prefetch to ensure animations don't cause layout shifts
  • No heavy JavaScript dependencies in toast rendering path

Image Optimization Strategy

While toasts typically don't contain images, any icon assets should:

  • Use SVG format for crisp rendering at all sizes
  • Be inlined or preloaded with appropriate loading="lazy" attributes if external
  • Support dark mode variants through CSS variables or theme switching

Font Loading Strategy

Toast notifications should:

  • Use system fonts for fast loading (-apple-system, BlinkMacSystemFont, etc.)
  • Include font-display: swap for any custom fonts to prevent FOIT
  • Avoid loading heavy font families just for toast text

Memoization Strategy

// Toast component memoization strategy
const Toast = React.memo(({ toast, onDismiss }: ToastProps) => {
  // Memoize computed values that don't change per render
  const icon = useMemo(() => getToastIcon(toast.type), [toast.type]);
  
  // Avoid unnecessary re-renders by memoizing callback
  const handleDismiss = useCallback(() => {
    onDismiss(toast.id);
  }, [onDismiss, toast.id]);

  return (
    <div className={`toast toast--${toast.type}`} role="alert">
      {icon}
      <span>{toast.message}</span>
      <button onClick={handleDismiss} aria-label="Dismiss notification">
        ✕
      </button>
    </div>
  );
});

Virtualization for Large Lists

For scenarios with many concurrent toasts:

// For toast lists exceeding 5-10 items, implement virtualization
const ToastList = ({ toasts }: { toasts: Toast[] }) => {
  const { height, width } = useWindowSize();
  
  return (
    <div className="toast-container">
      {toasts.length > 5 ? (
        <FixedSizeList
          height={height * 0.8}
          width={width * 0.3}
          itemCount={toasts.length}
          itemSize={60}
        >
          {({ index, style }) => (
            <div style={style}>
              <Toast toast={toasts[index]} />
            </div>
          )}
        </FixedSizeList>
      ) : (
        toasts.map(toast => <Toast key={toast.id} toast={toast} />)
      )}
    </div>
  );
};

Prefetching and Preloading Strategy

// Preload toast styles and animations
const preloadToastAssets = () => {
  // Preload CSS for toast animations
  const link = document.createElement('link');
  link.rel = 'preload';
  link.as = 'style';
  link.href = '/styles/toast-animations.css';
  document.head.appendChild(link);

  // Preload icon assets if needed
  const iconLink = document.createElement('link');
  iconLink.rel = 'prefetch';
  iconLink.href = '/icons/toast-icons.svg';
  document.head.appendChild(iconLink);
};

Animation Performance Considerations

Toasts should use CSS transforms and opacity for animations:

/* Use transform and opacity for hardware acceleration */
.toast {
  transition: 
    transform 0.2s ease-out,
    opacity 0.2s ease-out;
}

.toast-enter {
  transform: translateY(-100%);
  opacity: 0;
}

.toast-enter-active {
  transform: translateY(0);
  opacity: 1;
}

.toast-exit {
  transform: translateY(0);
  opacity: 1;
}

.toast-exit-active {
  transform: translateY(-100%);
  opacity: 0;
}

Memory Management

Toast system should clean up:

  • Expired toasts from memory after animation completion
  • Event listeners on dismiss buttons
  • DOM nodes when toast components unmount
  • References to dismissed toast items in state management

Resource Usage Targets

  • Toast creation: <5ms per toast
  • Toast dismissal animation: <200ms
  • Memory footprint: <500 bytes per toast instance
  • DOM node count: <100 active toasts max (with overflow handling)
  • CPU usage during toast operations: <5% of main thread time

7. Error Handling & Resilience

Error Boundary Placement Strategy

Error boundaries should be strategically placed to prevent cascading failures while maintaining user experience. For toast notifications, we'll implement error boundaries at the component level where toasts are rendered and at the application level for global error handling.

graph TD
    A[App Root] --> B[ToastContainer]
    B --> C[ToastList]
    C --> D[IndividualToast]
    A --> E[ErrorBoundary]
    E --> F[GlobalFallbackUI]

Global Error Handler Design

The global error handler will catch unhandled errors and display appropriate toast notifications. It will integrate with React's ErrorBoundary and provide fallback UI for critical failures.

// GlobalErrorHandler.tsx
import React from 'react';

interface ErrorBoundaryState {
  hasError: boolean;
  error?: Error | null;
}

class GlobalErrorHandler extends React.Component<{}, ErrorBoundaryState> {
  constructor(props: {}) {
    super(props);
    this.state = { hasError: false, error: null };
  }

  static getDerivedStateFromError(error: Error): ErrorBoundaryState {
    return { hasError: true, error };
  }

  componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {
    // Log error to monitoring service
    console.error('Global Error:', error, errorInfo);
    
    // Show user-friendly toast for critical errors
    if (typeof window !== 'undefined') {
      // Assuming we have a toast service available globally
      toast.error('Something went wrong. Please try again.', {
        autoClose: 5000,
        hideProgressBar: false,
        closeOnClick: true,
        pauseOnHover: true,
        draggable: true,
      });
    }
  }

  render(): React.ReactNode {
    if (this.state.hasError) {
      return (
        <div className="error-fallback">
          <h2>Something went wrong</h2>
          <p>We're working to fix the issue. Please try again later.</p>
        </div>
      );
    }

    return this.props.children;
  }
}

export default GlobalErrorHandler;

Offline / Network Failure Handling

The toast system must gracefully handle network failures and offline states:

// OfflineToastManager.ts
import { toast } from 'react-toastify';

class OfflineToastManager {
  private offlineToasts: Set<string> = new Set();
  
  showOfflineToast(message: string = 'You are offline') {
    const toastId = `offline-${Date.now()}`;
    
    if (this.offlineToasts.has(toastId)) return;
    
    this.offlineToasts.add(toastId);
    
    toast(message, {
      toastId,
      type: 'warning',
      autoClose: false,
      closeOnClick: false,
      pauseOnHover: false,
      draggable: false,
      className: 'offline-toast',
    });
  }

  clearOfflineToasts() {
    this.offlineToasts.forEach(toastId => {
      toast.dismiss(toastId);
    });
    this.offlineToasts.clear();
  }
}

const offlineManager = new OfflineToastManager();

// In your app's network monitoring logic
window.addEventListener('offline', () => {
  offlineManager.showOfflineToast();
});

window.addEventListener('online', () => {
  offlineManager.clearOfflineToasts();
});

Graceful Degradation Patterns

When toast services are unavailable, the system should fall back to basic UI notifications:

// FallbackToastService.ts
interface ToastOptions {
  autoClose?: number | boolean;
  hideProgressBar?: boolean;
  closeOnClick?: boolean;
  pauseOnHover?: boolean;
  draggable?: boolean;
  type?: 'success' | 'error' | 'info' | 'warning';
}

class FallbackToastService {
  private toastContainer: HTMLDivElement | null = null;

  constructor() {
    this.createToastContainer();
  }

  private createToastContainer() {
    if (typeof document === 'undefined') return;
    
    const container = document.createElement('div');
    container.className = 'fallback-toast-container';
    container.style.position = 'fixed';
    container.style.top = '20px';
    container.style.right = '20px';
    container.style.zIndex = '9999';
    document.body.appendChild(container);
    this.toastContainer = container;
  }

  show(message: string, options: ToastOptions = {}) {
    if (!this.toastContainer) return;

    const toastElement = document.createElement('div');
    toastElement.className = `fallback-toast fallback-toast--${options.type || 'info'}`;
    toastElement.textContent = message;
    
    // Apply styles based on options
    Object.assign(toastElement.style, {
      backgroundColor: options.type === 'error' ? '#f44336' : 
                     options.type === 'success' ? '#4caf50' : 
                     options.type === 'warning' ? '#ff9800' : '#2196f3',
      color: 'white',
      padding: '12px 16px',
      borderRadius: '4px',
      marginBottom: '8px',
      boxShadow: '0 2px 8px rgba(0,0,0,0.15)',
      opacity: '0',
      transform: 'translateX(100%)',
      transition: 'all 0.3s ease-in-out',
    });

    // Animate in
    setTimeout(() => {
      toastElement.style.opacity = '1';
      toastElement.style.transform = 'translateX(0)';
    }, 10);

    this.toastContainer.appendChild(toastElement);

    // Auto-close if specified
    if (options.autoClose !== false) {
      const timeout = typeof options.autoClose === 'number' ? options.autoClose : 3000;
      setTimeout(() => {
        toastElement.style.opacity = '0';
        toastElement.style.transform = 'translateX(100%)';
        setTimeout(() => {
          if (toastElement.parentNode) {
            toastElement.parentNode.removeChild(toastElement);
          }
        }, 300);
      }, timeout);
    }

    // Remove toast after animation completes
    setTimeout(() => {
      if (toastElement.parentNode) {
        toastElement.parentNode.removeChild(toastElement);
      }
    }, 3500);
  }

  error(message: string, options?: ToastOptions) {
    this.show(message, { ...options, type: 'error' });
  }

  success(message: string, options?: ToastOptions) {
    this.show(message, { ...options, type: 'success' });
  }

  warning(message: string, options?: ToastOptions) {
    this.show(message, { ...options, type: 'warning' });
  }

  info(message: string, options?: ToastOptions) {
    this.show(message, { ...options, type: 'info' });
  }
}

// Global fallback instance
const fallbackToastService = new FallbackToastService();

export { fallbackToastService };

User Feedback on Errors

Toasts should provide clear feedback when errors occur:

// ToastWithErrorHandling.tsx
import React from 'react';
import { toast } from 'react-toastify';

interface ToastWithErrorHandlingProps {
  title: string;
  message: string;
  type?: 'success' | 'error' | 'info' | 'warning';
  autoClose?: number | boolean;
  retryAction?: () => void;
}

const ToastWithErrorHandling: React.FC<ToastWithErrorHandlingProps> = ({
  title,
  message,
  type = 'info',
  autoClose = 5000,
  retryAction,
}) => {
  const handleRetry = () => {
    if (retryAction) {
      retryAction();
      toast.dismiss(); // Dismiss current toast
    }
  };

  const toastConfig = {
    type,
    autoClose,
    hideProgressBar: false,
    closeOnClick: true,
    pauseOnHover: true,
    draggable: true,
  };

  if (type === 'error' && retryAction) {
    toast.error(
      <div>
        <strong>{title}</strong>
        <p>{message}</p>
        <button 
          onClick={handleRetry}
          style={{
            marginTop: '8px',
            padding: '4px 8px',
            backgroundColor: '#f44336',
            color: 'white',
            border: 'none',
            borderRadius: '4px',
            cursor: 'pointer'
          }}
        >
          Retry
        </button>
      </div>,
      toastConfig
    );
  } else {
    toast[type](message, toastConfig);
  }
};

export default ToastWithErrorHandling;

Error Recovery Mechanisms

Implement retry logic for toast operations:

// RetryableToastService.ts
class RetryableToastService {
  private maxRetries = 3;
  private retryDelay = 1000;

  async showWithRetry(
    toastFn: () => Promise<void>,
    maxRetries?: number,
    delay?: number
  ): Promise<void> {
    const retries = maxRetries || this.maxRetries;
    const delayMs = delay || this.retryDelay;

    for (let i = 0; i <= retries; i++) {
      try {
        await toastFn();
        return;
      } catch (error) {
        if (i === retries) {
          // Final attempt failed, show fallback
          console.error('Toast operation failed after all retries:', error);
          toast.error('Failed to show notification. Please try again.');
          return;
        }
        
        // Wait before retrying
        await new Promise(resolve => setTimeout(resolve, delayMs * Math.pow(2, i)));
      }
    }
  }

  async showSuccessWithRetry(message: string) {
    await this.showWithRetry(() => toast.success(message));
  }

  async showErrorWithRetry(message: string) {
    await this.showWithRetry(() => toast.error(message));
  }
}

const retryableToastService = new RetryableToastService();
export { retryableToastService };

8. Testing Strategy

Unit Testing Approach

Component-level testing (70% coverage target):

  • Toast component rendering with different types (success, error, warning, info)
  • Toast lifecycle management (auto-dismiss, manual dismiss)
  • Toast content rendering with various message formats
  • Toast positioning and animation effects
  • Focus management for accessibility compliance

Utility function testing (80% coverage target):

  • Toast queue management logic
  • Toast ID generation and collision handling
  • Toast type validation and transformation
  • Dismiss timeout calculation and cleanup

Mocking strategy:

// Mock DOM APIs for animation testing
const mockAnimation = {
  addEventListener: jest.fn(),
  removeEventListener: jest.fn(),
  play: jest.fn(),
  pause: jest.fn()
};

Object.defineProperty(global, 'Animation', {
  writable: false,
  value: jest.fn().mockImplementation(() => mockAnimation)
});

Integration Testing Strategy

Toast manager integration:

  • End-to-end toast creation and dismissal flow
  • Concurrent toast queue behavior (max concurrent limit)
  • Toast stacking and positioning calculations
  • State synchronization between multiple toast instances

Cross-component integration:

  • Toast display with API error handling
  • Toast visibility with route changes
  • Toast persistence during app state transitions

E2E Testing Scope

Cypress test coverage (target: 85% of user flows):

  • Basic toast display and dismissal
  • Toast queue overflow handling (max 5 toasts)
  • Toast persistence across navigation
  • Keyboard accessibility testing (Tab/Enter/Escape)
  • Screen reader compatibility verification

Critical user journeys:

  1. API error triggers toast display
  2. User dismisses toast manually
  3. Toast auto-dismisses after timeout
  4. Multiple concurrent toasts display correctly

Visual Regression Testing

Screenshot testing strategy:

  • All toast types (success, error, warning, info) in default and custom positions
  • Toast stacking scenarios with 3+ concurrent toasts
  • RTL layout variations for internationalized content
  • Responsive breakpoints (mobile, tablet, desktop)

Accessibility Testing Automation

axe-core integration:

import { axe } from 'axe-core';

test('Toast component passes accessibility checks', async () => {
  const { container } = render(<Toast type="success" message="Success!" />);
  const results = await axe.run(container);
  expect(results.violations).toHaveLength(0);
});

Keyboard navigation coverage:

  • Tab navigation through toast controls
  • Enter/Space key for dismiss action
  • Escape key for immediate dismissal
  • Focus trapping within toast container

Test Data Strategy

Test fixtures:

// test/fixtures/toast-fixtures.ts
export const toastFixtures = {
  success: {
    type: 'success',
    message: 'Operation completed successfully',
    autoDismiss: true,
    timeout: 5000
  },
  error: {
    type: 'error',
    message: 'Failed to save changes',
    autoDismiss: false,
    timeout: 0
  },
  warning: {
    type: 'warning',
    message: 'Please review your input',
    autoDismiss: true,
    timeout: 8000
  }
};

Test data diversity:

  • Short and long message content (100+ character limits)
  • Special characters and emoji support
  • Multiline message rendering
  • RTL language content (Arabic, Hebrew)
  • Empty and null state handling

Test data generation:

  • Random ID generation for toast uniqueness
  • Timestamp-based auto-dismiss calculation
  • Position coordinate validation (top-left, top-right, etc.)
  • Concurrent queue state management testing

9. Accessibility (A11y)

WCAG 2.1 AA compliance checklist for this feature

The toast/notification system must meet the following WCAG 2.1 AA requirements:

  • 1.1.1 Non-text Content: All toast icons must have appropriate ARIA labels or be announced by screen readers
  • 1.3.1 Info and Relationships: Toasts must maintain proper semantic structure for screen readers
  • 1.4.1 Use of Color: Toasts must be distinguishable by color contrast (4.5:1 for normal text, 3:1 for large text)
  • 1.4.3 Contrast (Minimum): Toasts must meet minimum contrast ratios for text and backgrounds
  • 1.4.12 Text Spacing: Toasts must support text spacing adjustments without content overflow
  • 1.4.13 Content on Hover/Focus: Toasts must not rely solely on hover/focus for critical information
  • 1.4.13 Content on Hover/Focus: Toasts must not disappear automatically without user control
  • 2.1.1 Keyboard: All toast interactions must be keyboard accessible (close button, focus management)
  • 2.4.1 Bypass Blocks: Toasts must not interfere with keyboard navigation flow
  • 3.1.1 Language: Toast content language must be properly marked if different from default
  • 4.1.2 Name, Role, Value: Toast DOM elements must have proper ARIA roles and states

Keyboard navigation support

All toast interactions must be fully keyboard accessible:

  • Focus Management: Toasts should receive focus when they appear, and focus should be trapped within the toast until dismissed
  • Tab Navigation: Users must be able to tab through toast elements (close button, action buttons)
  • Escape Key: Pressing Escape should dismiss the toast and return focus to the previous element
  • Close Button: Close button must be keyboard accessible with proper ARIA attributes

Focus management strategy

// ToastManager component focus management
const useToastFocusManagement = () => {
  const [activeToastId, setActiveToastId] = useState<string | null>(null);
  
  const handleToastOpen = (toastId: string) => {
    setActiveToastId(toastId);
    // Focus the toast container for screen readers
    setTimeout(() => {
      const toastElement = document.getElementById(`toast-${toastId}`);
      if (toastElement) {
        toastElement.focus();
      }
    }, 0);
  };

  const handleToastClose = () => {
    setActiveToastId(null);
    // Return focus to previous active element
    const previousActiveElement = document.activeElement as HTMLElement;
    if (previousActiveElement) {
      previousActiveElement.focus();
    }
  };

  return { activeToastId, handleToastOpen, handleToastClose };
};

ARIA roles and attributes plan

Each toast must implement appropriate ARIA attributes:

// Toast component ARIA implementation
interface ToastProps {
  id: string;
  type: 'success' | 'error' | 'warning' | 'info';
  message: string;
  title?: string;
  autoClose?: boolean;
  duration?: number;
  onClose: () => void;
}

const Toast = ({ 
  id, 
  type, 
  message, 
  title, 
  autoClose = true, 
  duration = 5000,
  onClose 
}: ToastProps) => {
  const [isVisible, setIsVisible] = useState(true);
  
  // ARIA roles and attributes
  const role = type === 'error' ? 'alert' : 'status';
  const ariaLive = type === 'error' ? 'assertive' : 'polite';
  const ariaLabel = type === 'error' ? 'Error notification' : 
                   type === 'success' ? 'Success notification' : 
                   type === 'warning' ? 'Warning notification' : 
                   'Information notification';
  
  return (
    <div
      id={`toast-${id}`}
      role={role}
      aria-live={ariaLive}
      aria-atomic="true"
      aria-label={ariaLabel}
      className={`toast toast--${type}`}
      tabIndex={0} // Make toast focusable
      onFocus={() => {
        // Pause auto-close when focused
        if (autoClose) {
          // Implementation for pause logic
        }
      }}
      onBlur={() => {
        // Resume auto-close when focus lost
        if (autoClose) {
          // Implementation for resume logic
        }
      }}
    >
      <div className="toast__content">
        {title && (
          <h3 
            className="toast__title"
            aria-label={title}
          >
            {title}
          </h3>
        )}
        <p 
          className="toast__message"
          aria-label={message}
        >
          {message}
        </p>
      </div>
      
      <button
        className="toast__close-button"
        aria-label="Close notification"
        onClick={onClose}
        onKeyDown={(e) => {
          if (e.key === 'Enter' || e.key === ' ') {
            onClose();
          }
        }}
      >
        <span aria-hidden="true">×</span>
      </button>
    </div>
  );
};

Screen reader compatibility

  • Announcement Priority: Error toasts should be announced with aria-live="assertive" while info/warning toasts use aria-live="polite"
  • Content Announcements: Toast content should be announced immediately upon appearance
  • Dynamic Updates: When toast content changes dynamically, proper ARIA updates must be handled
  • Focus Trapping: Screen reader focus should remain within the toast until dismissed

Additional accessibility considerations

  • Announcement Delay: Toasts should not interrupt ongoing screen reader announcements
  • Multiple Toast Support: When multiple toasts appear, they should be announced in order of appearance
  • Custom Content Handling: Toasts with custom content must ensure proper semantic structure for screen readers
  • Time-based Content: Auto-closing toasts should provide appropriate time context for screen reader users
  • Visual vs. Aural: Ensure that visually apparent information (like color coding) is also conveyed through screen reader announcements

Keyboard accessibility patterns

// Toast keyboard navigation handler
const useToastKeyboardNavigation = () => {
  const handleKeyDown = (e: KeyboardEvent, toastId: string) => {
    if (e.key === 'Escape') {
      // Dismiss toast and restore focus
      const toastElement = document.getElementById(`toast-${toastId}`);
      if (toastElement) {
        toastElement.blur();
      }
      // Call dismiss handler
    }
    
    if (e.key === 'Tab') {
      // Handle tab navigation within toast
      const closeBtn = document.querySelector(`[aria-label="Close notification"]`);
      if (closeBtn && document.activeElement === closeBtn) {
        // Move focus to first element after toast or return to previous focus
      }
    }
  };

  return { handleKeyDown };
};

The system must support all keyboard navigation patterns expected by users with disabilities and provide equivalent functionality to mouse-based interactions.

10. Security Considerations

XSS Prevention Strategy

The toast system must prevent cross-site scripting attacks through strict input sanitization and output encoding:

// Secure toast content handling
const sanitizeToastContent = (content: string): string => {
  const div = document.createElement('div');
  div.textContent = content;
  return div.innerHTML;
};

// Example of secure toast creation
const createSecureToast = (message: string, type: ToastType) => {
  const sanitizedMessage = sanitizeToastContent(message);
  return {
    id: generateId(),
    message: sanitizedMessage,
    type,
    timestamp: Date.now()
  };
};

CSRF Protection

While toasts themselves don't typically handle authentication, the toast system should not expose sensitive data that could be exploited:

  • Toasts should never display raw API responses or internal error stack traces
  • All toast messages must be sanitized and user-friendly before display
  • No sensitive session or authentication tokens should appear in toast content

Content Security Policy (CSP) Headers

The frontend must respect CSP policies to prevent script injection in toast content:

// CSP policy enforcement for toast content
const validateToastContent = (content: string): boolean => {
  // Check for dangerous patterns that could indicate XSS
  const dangerousPatterns = [
    /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
    /on\w+\s*=\s*["'][^"']*["']/gi,
    /javascript:/gi
  ];
  
  return !dangerousPatterns.some(pattern => pattern.test(content));
};

Sensitive Data Handling in Frontend State

Toast messages should never contain sensitive information:

// Toast state management with sensitive data filtering
interface ToastState {
  id: string;
  message: string; // Sanitized user-facing message
  type: 'success' | 'error' | 'warning' | 'info';
  timestamp: number;
  duration?: number;
  dismissible?: boolean;
}

// Filter sensitive data from toast content
const filterSensitiveData = (message: string): string => {
  // Remove any potential sensitive data patterns
  return message.replace(/\b(?:password|token|secret|api_key)\b/gi, '[REDACTED]');
};

Input Sanitization Approach

All toast content must undergo strict sanitization:

// Comprehensive input sanitization for toast messages
const sanitizeToastInput = (input: string): string => {
  // HTML escape all special characters
  const escaped = input
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#x27;');
  
  // Remove any remaining dangerous HTML tags
  return escaped.replace(/<[^>]*>/g, '');
};

// Usage in toast creation
const createToast = (message: string, type: ToastType) => {
  const sanitizedMessage = sanitizeToastInput(message);
  
  return {
    id: generateId(),
    message: sanitizedMessage,
    type,
    timestamp: Date.now()
  };
};

Security Best Practices Implementation

  1. Never trust user input - All toast messages must be sanitized before display
  2. Validate content length - Prevent oversized content that could cause rendering issues
  3. Implement proper escaping - HTML and script tag escaping for all content
  4. Restrict DOM manipulation - Toasts should not allow arbitrary DOM injection
  5. Secure ID generation - Use cryptographically secure IDs to prevent ID guessing attacks

Attack Surface Reduction

The toast system should not:

  • Accept raw HTML content from external sources
  • Display API error responses directly to users
  • Allow arbitrary JavaScript execution through toast content
  • Expose internal system information in user-facing messages

All toast content should be processed through a security filter that ensures only safe, user-friendly text is displayed to end users.

11. Internationalization (i18n)

Translation Key Structure and Naming Convention

Following BEM-style naming with feature-scoped keys to prevent collisions:

// Translation keys structure
{
  "toast": {
    "success": "Success",
    "error": "Error",
    "warning": "Warning",
    "info": "Info",
    "dismiss": "Dismiss",
    "defaultTitle": "Notification",
    "defaultDescription": "Your notification message here"
  },
  "toast:success:created": "Item created successfully",
  "toast:error:network": "Network error occurred",
  "toast:warning:timeout": "Request timeout exceeded"
}

RTL Support Considerations

Toast notifications will automatically adapt to RTL layout using CSS logical properties:

/* Toast container with RTL support */
.toast-container {
  /* Will automatically adjust based on text direction */
  left: auto;
  right: auto;
  padding-inline-start: 1rem;
  padding-inline-end: 1rem;
}

/* RTL-aware positioning */
[dir="rtl"] .toast-container {
  left: auto;
  right: 1rem;
}

Date, Number, Currency Formatting Strategy

Toast messages may include time-based content that requires proper localization:

// Example of localized timestamp in toast
const formatToastTimestamp = (date: Date, locale: string) => {
  return new Intl.DateTimeFormat(locale, {
    hour: '2-digit',
    minute: '2-digit',
    second: '2-digit'
  }).format(date);
};

// Example of localized numeric values in toast
const formatToastCount = (count: number, locale: string) => {
  return new Intl.NumberFormat(locale).format(count);
};

Locale Detection and Switching Strategy

Automatic locale detection using browser navigator:

// Auto-detect user's preferred language
const detectUserLocale = (): string => {
  const browserLang = navigator.language || navigator.languages[0];
  const supportedLocales = ['en', 'es', 'fr', 'de', 'ja'];
  
  // Match to supported locales or default to English
  return supportedLocales.includes(browserLang) 
    ? browserLang 
    : 'en';
};

// Manual locale switching with persistence
const switchLocale = (newLocale: string) => {
  localStorage.setItem('preferred-locale', newLocale);
  // Trigger re-render of all localized content
  window.location.reload();
};

Translation Key Management

Implementation uses a centralized translation key registry:

// Translation keys enum for type safety
export enum ToastTranslationKeys {
  SUCCESS = 'toast.success',
  ERROR = 'toast.error',
  WARNING = 'toast.warning',
  INFO = 'toast.info',
  DISMISS = 'toast.dismiss',
  DEFAULT_TITLE = 'toast.defaultTitle',
  DEFAULT_DESCRIPTION = 'toast.defaultDescription'
}

// Toast-specific translation keys
export enum ToastMessageKeys {
  CREATED = 'toast:success:created',
  NETWORK_ERROR = 'toast:error:network',
  TIMEOUT_WARNING = 'toast:warning:timeout'
}

i18n Integration Points

Toast system will integrate with existing i18n provider through context:

// Context for providing translation functions to toast components
interface ToastI18nContext {
  t: (key: string, params?: Record<string, any>) => string;
  locale: string;
}

const ToastI18nContext = createContext<ToastI18nContext>({
  t: (key) => key,
  locale: 'en'
});

Message Template Handling

Support for parameterized messages with proper pluralization:

// Example of localized message with parameters
const getLocalizedMessage = (
  key: string,
  params?: { count?: number; name?: string },
  locale: string = 'en'
): string => {
  // Fetch translation with interpolation
  const baseMessage = t(key, params);
  
  // Handle pluralization for count-based messages
  if (params?.count !== undefined) {
    const pluralForm = getPluralForm(params.count, locale);
    return baseMessage.replace('{count}', params.count.toString());
  }
  
  return baseMessage;
};

Content Direction Adaptation

Toast components will adapt to content direction:

// Component that respects text direction
const ToastContent = ({ 
  message, 
  direction = 'ltr' 
}: { 
  message: string; 
  direction?: 'ltr' | 'rtl' 
}) => {
  return (
    <div 
      className="toast-content"
      dir={direction}
      style={{ 
        textAlign: direction === 'rtl' ? 'right' : 'left' 
      }}
    >
      {message}
    </div>
  );
};

12. Monitoring & Observability

Real User Monitoring (RUM) Metrics to Track

The toast system should capture key performance indicators to understand user experience:

  • Toast Display Time: How long users see toasts before they disappear or are dismissed
  • Toast Success Rate: Percentage of toasts that successfully display vs. failed displays
  • User Interaction Rate: % of toasts that receive user action (click, dismiss) vs. auto-dismissal
  • Toast Queue Latency: Time from toast creation to actual display in UI queue
  • Toast Failure Rate: % of toast creation attempts that fail due to system constraints

Error Tracking Integration

Integrate with error monitoring platforms (Sentry, Datadog) to capture:

  • Toast rendering failures (DOM errors, context provider issues)
  • API integration errors when toast content depends on external data
  • Memory leak patterns in toast queue management
  • Race conditions in concurrent toast creation

Custom Events and User Journey Tracking

Implement event tracking for key user interactions:

// Toast event tracking interface
interface ToastEvent {
  eventType: 'toast_displayed' | 'toast_dismissed' | 'toast_action_clicked' | 'toast_error';
  toastId: string;
  timestamp: number;
  userId?: string;
  context?: Record<string, any>;
}

// Example implementation in toast manager
const trackToastEvent = (event: ToastEvent) => {
  // Send to analytics service or RUM provider
  if (window?.analytics) {
    window.analytics.track('toast_event', event);
  }
};

Performance Monitoring and Regression Detection

Monitor toast system performance to detect regressions:

  • Memory Usage: Track toast queue size and DOM node count over time
  • GC Pressure: Monitor garbage collection frequency when many toasts are active
  • Render Performance: Measure toast component render times and frame drops
  • Queue Processing Time: Track time to process new toast additions to queue

Implement performance monitoring hooks:

// Performance monitoring for toast system
const useToastPerformance = () => {
  const [metrics, setMetrics] = useState({
    queueSize: 0,
    renderTime: 0,
    memoryUsage: 0
  });

  useEffect(() => {
    // Periodic performance sampling
    const interval = setInterval(() => {
      const queueSize = toastQueue.length;
      const memoryUsage = getMemoryUsage(); // Pseudo-code for memory measurement
      
      setMetrics({
        queueSize,
        renderTime: calculateRenderTime(),
        memoryUsage
      });
      
      // Send to performance monitoring service
      if (window?.performance) {
        window.performance.mark('toast_metrics_update');
      }
    }, 5000);

    return () => clearInterval(interval);
  }, []);

  return metrics;
};

Alerting and Thresholds

Configure alerting thresholds for toast system health:

  • Queue Size Alert: > 20 toasts in queue triggers alert
  • Render Time Alert: > 100ms per toast render triggers performance alert
  • Failure Rate Alert: > 5% toast creation failures trigger error alert
  • Memory Leak Alert: Memory usage increasing by > 1MB over 5 minutes

Data Collection Strategy

Implement data collection with privacy considerations:

// Privacy-aware telemetry configuration
const toastTelemetryConfig = {
  enabled: true,
  collectUserIds: false,
  sampleRate: 0.1, // 10% of sessions
  anonymize: true,
  maxQueueSize: 50,
  retentionDays: 30
};

Dashboard and Visualization

Create dashboards to visualize toast system health:

  • Toast Volume Dashboard: Real-time toast creation rate
  • Queue Health Dashboard: Queue size, processing latency
  • User Interaction Dashboard: Dismissal rates, action click-through rates
  • Error Rate Dashboard: Failure and error occurrence trends

Integration with Existing Monitoring Infrastructure

The toast monitoring system should integrate with existing observability tools:

  • Log Aggregation: Toast events logged to centralized logging system
  • Metric Collection: Prometheus/Grafana compatible metrics for toast performance
  • Trace Integration: Correlate toast events with backend request traces
  • Alerting Integration: Connect to existing alerting systems (PagerDuty, Slack)

Data Retention and Compliance

Implement proper data handling:

  • Compliance: Ensure no PII in toast telemetry
  • Retention Policy: 30-day retention for performance data, 7-day for error logs
  • Data Export: Support for data export to external monitoring systems
  • Privacy Controls: Allow users to opt-out of telemetry collection

Health Checks and Self-Monitoring

Implement built-in health checks:

// Toast system health check
const toastHealthCheck = () => {
  const health = {
    queueHealthy: toastQueue.length < 50,
    memoryHealthy: getMemoryUsage() < 100 * 1024 * 1024, // 100MB
    renderHealthy: calculateRenderTime() < 50,
    errorRate: getErrorRate(),
    uptime: Date.now() - systemStartTime
  };

  return health;
};

Rollback and Recovery Mechanisms

Implement monitoring for system recovery:

  • Graceful Degradation: When toast system fails, fall back to basic console logging
  • Recovery Metrics: Track time to recovery from toast system failures
  • Fallback Behavior: Monitor when fallback logging is triggered due to toast failure

13. File & Folder Structure

Proposed Directory Structure

src/
├── components/
│   └── Toast/
│       ├── Toast.tsx
│       ├── ToastContainer.tsx
│       ├── ToastProvider.tsx
│       ├── toastTypes.ts
│       └── index.ts
├── hooks/
│   └── useToast.ts
├── contexts/
│   └── ToastContext.ts
├── types/
│   └── toast.ts
└── utils/
    └── toastUtils.ts

Module Boundaries and Import Rules

  • ToastProvider and ToastContext are exported from components/Toast/index.ts to provide a clean API for consumers
  • useToast hook is exported from hooks/index.ts to maintain separation of concerns between presentation and logic
  • toastTypes.ts and toastUtils.ts are internal utilities that can be imported by any component in the toast module but not by other modules
  • No cross-feature imports are allowed to maintain modular boundaries

Shared vs Feature-Specific Code Separation

The toast system is designed as a shared component that can be used across the application without feature-specific logic. It's built to be generic enough that it can handle various toast types (success, error, warning, info) and configurations while maintaining clean separation from business logic. The system is designed to be imported and used by any feature module without tight coupling.

The structure follows a feature-based organization where the toast system is contained within its own module, making it easy to test in isolation and maintain as a self-contained unit.

14. Dependencies & Third-Party Libraries

Key Libraries Selected with Justification

1. React (v18+)

  • Justification: Core library for component-based UI development, essential for building toast components with proper lifecycle management and state handling.
  • Bundle Impact: ~150KB (minified) - unavoidable dependency for React applications.

2. react-transition-group (v4+)

  • Justification: Provides robust animation and transition handling for toast appearance/disappearance with proper DOM cleanup. Essential for smooth user experience without relying on CSS animations that might conflict.
  • Bundle Impact: ~15KB (minified) - lightweight and purpose-built for this use case.

3. clsx (v2+)

  • Justification: Utility for conditionally joining CSS class names, enabling clean and readable styling logic without complex ternary operators or string concatenation.
  • Bundle Impact: ~1KB (minified) - minimal overhead with maximum utility.

4. @types/react (v18+)

  • Justification: TypeScript type definitions for React, ensuring type safety and better developer experience in toast component development.
  • Bundle Impact: No runtime impact - only type definitions.

Alternatives Considered and Rejected

1. CSS-in-JS Libraries (styled-components, emotion)

  • Rejected: Overkill for toast system which requires minimal styling. Adds significant bundle size (200-500KB) and complexity for simple UI elements.
  • Trade-off: Bundle size vs. developer experience.

2. Framer Motion

  • Rejected: While powerful for animations, toast system only needs simple enter/exit transitions. Framer Motion adds ~20KB+ bundle size and introduces unnecessary complexity for basic animation needs.
  • Trade-off: Animation flexibility vs. performance.

3. Material UI Toast Components

  • Rejected: Would introduce unnecessary dependencies and styling conflicts with existing design systems. Toasts are a minimal UI element that should be lightweight and customizable.
  • Trade-off: Pre-built functionality vs. customizability.

Licensing and Bundle Size Impact Assessment

Bundle Size Budget Allocation:

  • Core React + react-transition-group + clsx = ~165KB total (minimal impact on overall bundle size)
  • All dependencies are MIT licensed with no restrictions for production use
  • No third-party runtime dependencies beyond React ecosystem

Performance Impact:

  • All selected libraries are lightweight and purpose-built
  • No runtime overhead for toast functionality beyond what's necessary
  • Bundle size increase is negligible (under 0.5% of typical React application bundle)

15. Open Questions, Risks & Trade-offs

Decisions that need product/engineering alignment

  • Toast stacking behavior: Should toasts stack vertically or replace older ones? What's the maximum concurrent toast limit?
  • Auto-dismiss timing: What default durations should be used for different toast types (success: 5s, error: 10s, info: 30s)?
  • User interaction handling: Should toasts be dismissible by user click or only auto-dismiss? How should hover/pause behavior work?
  • Toast persistence: Should critical errors or important notifications persist until user action rather than auto-dismiss?

Known trade-offs made in this design

  • Simplicity vs. Feature Richness: Prioritized clean, minimal API over extensive customization options to avoid feature bloat and maintain performance
  • Global State vs. Scoped State: Used global state management for toast visibility but isolated component rendering to prevent cascading re-renders
  • Animation Performance vs. Visual Appeal: Chose simple CSS transitions over complex animations to maintain optimal Core Web Vitals scores
  • Type Safety vs. Flexibility: Strong typing for toast content but limited generic payload support to maintain type safety and prevent runtime errors

Risks and mitigations

  • Performance Degradation: Risk of excessive DOM nodes from too many concurrent toasts. Mitigation: Implement max toast limit with queueing strategy
  • Accessibility Issues: Risk of screen reader conflicts or keyboard navigation problems. Mitigation: Comprehensive ARIA implementation and thorough accessibility testing
  • State Management Conflicts: Risk of toast state conflicts in complex app with multiple notification sources. Mitigation: Centralized queue system with unique IDs and proper cleanup
  • Memory Leaks: Risk of toast references persisting after dismissal. Mitigation: Proper cleanup in useEffect and component unmount handlers

Technical debt acknowledged

  • Limited Customization: The current design doesn't support extensive theme customization or complex content rendering, which may require future refactoring
  • Static Positioning: Toasts are currently hard-coded to top-right position, limiting flexibility for different app layouts
  • Basic Error Recovery: Only basic retry logic for failed toast creation, no sophisticated error recovery patterns implemented
  • No Toast Grouping: Individual toasts are not grouped or batched, which could impact performance in high-volume scenarios
  • Limited Internationalization Support: Toast content is not fully prepared for i18n with dynamic message interpolation, requiring future enhancements

Was this article helpful?

Topics in this article

More from Frontend LLD

  • Frontend LLD

    Notification Panel Design System

    Comprehensive low-level design for a scalable, accessible notification panel component with real-time updates and user preferences.

  • Frontend LLD

    Tooltip Component Design and Implementation

    Comprehensive low-level design for a reusable, accessible tooltip component with dynamic positioning and performance optimizations

  • Frontend HLD

    Search and Autocomplete Frontend System Design

    Comprehensive frontend high-level design for scalable search and autocomplete functionality with real-time suggestions and performance optimization

Stay in the Loop

Get curated DSA patterns, coding problems, frontend and backend LLD/HLD, and AI/ML System Design insights delivered straight to your inbox. No spam, just focused interview and engineering content.