React / Types of React Portal / Toast Portal
Home /React /Types of React Portal /Toast Portal

Toast Portal

Toast notifications appear fixed at a corner of the viewport. A Portal renders them into a
dedicated container at the body level so they’re always visible regardless of the current
page scroll or component depth.

ToastContext.jsx: Global Toast Manager


import { createContext, useContext, useState, useCallback } from 'react';
import { createPortal } from 'react-dom';

const ToastContext = createContext(null);

let idCounter = 0;

export function ToastProvider({ children }) {
  const [toasts, setToasts] = useState([]);

  const addToast = useCallback((message, type = 'info', duration = 3000) => {
    const id = ++idCounter;
    setToasts(prev => [...prev, { id, message, type }]);
    setTimeout(() => {
      setToasts(prev => prev.filter(t => t.id !== id));
    }, duration);
  }, []);

  const removeToast = useCallback((id) => {
    setToasts(prev => prev.filter(t => t.id !== id));
  }, []);

  return (
    <ToastContext.Provider value={{ addToast }}>
      {children}

      {createPortal(
        <div
          aria-live="polite"
          aria-atomic="false"
          style={{
            position: 'fixed',
            bottom: '1rem',
            right: '1rem',
            display: 'flex',
            flexDirection: 'column',
            gap: '0.5rem',
            zIndex: 99999
          }}
        >
          {toasts.map(toast => (
            <div
              key={toast.id}
              role="alert"
              style={{
                padding: '0.75rem 1.25rem',
                background: toast.type === 'error' ? '#dc3545' : toast.type === 'success' ? '#28a745' : '#17a2b8',
                color: '#fff',
                borderRadius: '6px',
                cursor: 'pointer'
              }}
              onClick={() => removeToast(toast.id)}
            >
              {toast.message}
            </div>
          ))}
        </div>,
        document.body
      )}
    </ToastContext.Provider>
  );
}

export function useToast() {
  return useContext(ToastContext);
}

App.jsx: Using the Toast System


import { ToastProvider, useToast } from './ToastContext';

function Inner() {
  const { addToast } = useToast();

  return (
    <div>
      <button onClick={() => addToast('Operation successful!', 'success')}>Show Success</button>
      <button onClick={() => addToast('Something went wrong.', 'error')}>Show Error</button>
      <button onClick={() => addToast('Here is some info.', 'info')}>Show Info</button>
    </div>
  );
}

function App() {
  return (
    <ToastProvider>
      <Inner />
    </ToastProvider>
  );
}

export default App;

Key Concepts

  • Context + Portal: The portal lives inside a Context provider. Children anywhere in the tree can trigger toasts via useToast().
  • aria-live=”polite”: Screen readers announce new toast messages without interrupting the user.
  • Auto-dismiss: setTimeout removes toasts after the specified duration.