Tutorials Logic, IN +91 8092939553 info@tutorialslogic.com
FAQs Support
Navigation
Home About Us Contact Us Blogs FAQs
Tutorials
All Tutorials
Services
Academic Projects Resume Writing Interview Questions Website Development
Compiler Tutorials

React Portals

What are Portals?

Portals let you render a component's output into a different DOM node than its parent. This is essential for modals, tooltips, and dropdowns that need to visually "escape" their parent's CSS constraints (like overflow: hidden or z-index stacking contexts).

Even though the portal renders outside the parent DOM node, it still behaves like a normal React child — events bubble up through the React tree, not the DOM tree.

Portals — Modal, Tooltip, Toast Notification
import { createPortal } from 'react-dom'
import { useState, useEffect } from 'react'

// Modal component — renders into #modal-root, not inside parent
function Modal({ isOpen, onClose, title, children }) {
    // Close on Escape key
    useEffect(() => {
        if (!isOpen) return
        const handleEsc = (e) => { if (e.key === 'Escape') onClose() }
        document.addEventListener('keydown', handleEsc)
        // Prevent body scroll when modal is open
        document.body.style.overflow = 'hidden'
        return () => {
            document.removeEventListener('keydown', handleEsc)
            document.body.style.overflow = ''
        }
    }, [isOpen, onClose])

    if (!isOpen) return null

    // createPortal(children, domNode)
    return createPortal(
        <div
            className="modal-overlay"
            onClick={onClose}
            style={{
                position: 'fixed', inset: 0,
                background: 'rgba(0,0,0,0.5)',
                display: 'flex', alignItems: 'center', justifyContent: 'center',
                zIndex: 1000
            }}
        >
            <div
                className="modal-content"
                onClick={e => e.stopPropagation()}
                style={{
                    background: 'white', borderRadius: '8px',
                    padding: '24px', maxWidth: '500px', width: '90%'
                }}
            >
                <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '16px' }}>
                    <h2>{title}</h2>
                    <button onClick={onClose}>×</button>
                </div>
                {children}
            </div>
        </div>,
        document.getElementById('modal-root')  // render here, not in parent
    )
}

// Usage
function App() {
    const [isOpen, setIsOpen] = useState(false)

    return (
        <div style={{ overflow: 'hidden', height: '200px' }}>
            {/* Even with overflow:hidden on parent, modal renders correctly */}
            <button onClick={() => setIsOpen(true)}>Open Modal</button>

            <Modal isOpen={isOpen} onClose={() => setIsOpen(false)} title="Confirm Action">
                <p>Are you sure you want to delete this item?</p>
                <button onClick={() => setIsOpen(false)}>Cancel</button>
                <button onClick={() => { alert('Deleted!'); setIsOpen(false) }}>Delete</button>
            </Modal>
        </div>
    )
}
import { createPortal } from 'react-dom'
import { useState, useRef, useEffect } from 'react'

// Tooltip using portal — avoids z-index/overflow issues
function Tooltip({ children, text }) {
    const [visible, setVisible] = useState(false)
    const [position, setPosition] = useState({ top: 0, left: 0 })
    const triggerRef = useRef(null)

    const showTooltip = () => {
        const rect = triggerRef.current.getBoundingClientRect()
        setPosition({
            top: rect.top - 40 + window.scrollY,
            left: rect.left + rect.width / 2 + window.scrollX,
        })
        setVisible(true)
    }

    return (
        <>
            <span
                ref={triggerRef}
                onMouseEnter={showTooltip}
                onMouseLeave={() => setVisible(false)}
            >
                {children}
            </span>

            {visible && createPortal(
                <div style={{
                    position: 'absolute',
                    top: position.top,
                    left: position.left,
                    transform: 'translateX(-50%)',
                    background: '#333', color: 'white',
                    padding: '4px 8px', borderRadius: '4px',
                    fontSize: '12px', whiteSpace: 'nowrap',
                    zIndex: 9999, pointerEvents: 'none',
                }}>
                    {text}
                </div>,
                document.body
            )}
        </>
    )
}

// Toast notification system using portal
function ToastContainer({ toasts, removeToast }) {
    return createPortal(
        <div style={{ position: 'fixed', bottom: '20px', right: '20px', zIndex: 9999 }}>
            {toasts.map(toast => (
                <div key={toast.id} className={`toast toast-${toast.type}`}>
                    {toast.message}
                    <button onClick={() => removeToast(toast.id)}>×</button>
                </div>
            ))}
        </div>,
        document.body
    )
}
<!-- index.html — add portal mount points -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>React App</title>
</head>
<body>
    <!-- Main React app root -->
    <div id="root"></div>

    <!-- Portal mount points -->
    <div id="modal-root"></div>
    <div id="tooltip-root"></div>
    <div id="toast-root"></div>

    <!-- Portals render here, outside #root -->
    <!-- But React events still bubble through the React tree -->
</body>
</html>

Ready to Level Up Your Skills?

Explore 500+ free tutorials across 20+ languages and frameworks.