Portals

Hard

Portals render children into a DOM node outside the parent component's DOM hierarchy using createPortal. They are essential for modals, tooltips, and dropdowns that must visually escape overflow or z-index stacking contexts while maintaining React tree behavior like event bubbling and context access.

Interactive Visualization

Key Points

  • createPortal(children, domNode) renders into any DOM node
  • Portal children escape overflow:hidden and z-index stacking contexts
  • Events still bubble through the React tree, not the DOM tree
  • Context is still accessible from the React tree position
  • Common for modals, tooltips, toasts, and dropdown menus
  • The target DOM node must exist before the portal renders

Code Examples

Modal Portal

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

interface ModalProps {
  children: ReactNode
  isOpen: boolean
  onClose: () => void
}

function Modal({ children, isOpen, onClose }: ModalProps) {
  const overlayRef = useRef<HTMLDivElement>(null)

  useEffect(() => {
    if (!isOpen) return
    const handleEscape = (e: KeyboardEvent) => {
      if (e.key === 'Escape') onClose()
    }
    document.addEventListener('keydown', handleEscape)
    return () => document.removeEventListener('keydown', handleEscape)
  }, [isOpen, onClose])

  if (!isOpen) return null

  return createPortal(
    <div
      ref={overlayRef}
      onClick={(e) => {
        if (e.target === overlayRef.current) onClose()
      }}
      style={{
        position: 'fixed',
        inset: 0,
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        background: 'rgba(0,0,0,0.5)',
      }}
    >
      <div>{children}</div>
    </div>,
    document.body
  )
}

function App() {
  const [showModal, setShowModal] = useState(false)
  return (
    <div style={{ overflow: 'hidden' }}>
      <button onClick={() => setShowModal(true)}>Open Modal</button>
      <Modal isOpen={showModal} onClose={() => setShowModal(false)}>
        <h2>Modal Title</h2>
        <p>This escapes overflow:hidden</p>
      </Modal>
    </div>
  )
}

The modal renders into document.body via a portal, escaping any overflow or stacking context from its parent hierarchy

Common Mistakes

  • Forgetting that the portal target DOM node must exist before rendering
  • Not handling keyboard events like Escape for accessibility
  • Assuming portal events follow the DOM hierarchy instead of the React tree

Interview Tips

  • Explain that events bubble through the React tree, not the DOM tree, for portals
  • Know the use cases: modals, tooltips, toasts, anything that needs to escape stacking contexts
  • Discuss accessibility requirements for portal-based modals like focus trapping

Related Concepts