React / Types of React Portal / Modal Dialog Portal
Home /React /Types of React Portal /Modal Dialog Portal

Modal Dialog Portal

Modal dialogs are the most common use case for Portals. A modal needs to cover the entire
viewport, appear above all other content, and block interaction with the rest of the page.

index.html

<div id="root"></div>
<div id="modal-root"></div>

Modal.jsx – Reusable Modal Component

import { createPortal } from 'react-dom';
import { useEffect, useRef } from 'react';

function Modal({ isOpen, onClose, title, children }) {
  const overlayRef = useRef(null);

  // Close when clicking the backdrop (outside the dialog)
  function handleOverlayClick(e) {
    if (e.target === overlayRef.current) {
      onClose();
    }
  }

  // Close on Escape key press
  useEffect(() => {
    if (!isOpen) return;

    function handleKeyDown(e) {
      if (e.key === 'Escape') onClose();
    }

    document.addEventListener('keydown', handleKeyDown);
    return () => document.removeEventListener('keydown', handleKeyDown);
  }, [isOpen, onClose]);

  // Prevent background scroll while modal is open
  useEffect(() => {
    if (isOpen) {
      document.body.style.overflow = 'hidden';
    } else {
      document.body.style.overflow = '';
    }
    return () => { document.body.style.overflow = ''; };
  }, [isOpen]);

  if (!isOpen) return null;

  return createPortal(
    <div
      ref={overlayRef}
      onClick={handleOverlayClick}
      role="dialog"
      aria-modal="true"
      aria-labelledby="modal-title"
      style={{
        position: 'fixed',
        inset: 0,
        backgroundColor: 'rgba(0,0,0,0.5)',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        zIndex: 9999
      }}
    >
      <div style={{ background: '#fff', padding: '2rem', borderRadius: '8px', minWidth: '320px' }}>
        <h2 id="modal-title">{title}</h2>
        {children}
        <button onClick={onClose}>Close</button>
      </div>
    </div>,
    document.getElementById('modal-root')
  );
}

export default Modal;

App.jsx: Using the Modal

import { useState } from 'react';
import Modal from './Modal';

function App() {
  const [open, setOpen] = useState(false);

  return (
    <div>
      <h1>My Application</h1>
      <button onClick={() => setOpen(true)}>Open Modal</button>

      <Modal
        isOpen={open}
        onClose={() => setOpen(false)}
        title="Confirm Action"
      >
        <p>Are you sure you want to proceed?</p>
        <button onClick={() => setOpen(false)}>Yes, proceed</button>
      </Modal>
    </div>
  );
}

export default App;

Key Concepts in the Modal Example

  • Conditional rendering: Return null when isOpen is false, nothing is mounted in the portal target.
  • Backdrop click: Compare e.target with the overlay ref to close only when clicking outside the dialog box.
  • Escape key: Add a keydown listener inside a useEffect, clean it up on unmount.
  • Body overflow lock: Prevent page scroll while the modal is visible.
  • ARIA attributes: role="dialog", aria-modal="true", aria-labelledby for accessibility.