React - Portals

Overview

Estimated time: 15–25 minutes

Portals let you render UI into a DOM node that exists outside your component’s own DOM hierarchy (useful for modals, tooltips).

Learning Objectives

  • Understand how to render into an external container with createPortal.
  • Build a basic modal using portals.

Try it: Modal via Portal

View source
const { useState } = React;
function Modal({ children, onClose }){
  const el = document.getElementById('portal-root');
  return ReactDOM.createPortal(
    <div style={{position:'fixed', inset:0, background:'rgba(0,0,0,.5)'}} onClick={onClose}>
      <div style={{margin:'10% auto', padding:20, background:'#fff', color:'#111', width:300, borderRadius:8}} onClick={(e) => e.stopPropagation()}>
        {children}
      </div>
    </div>,
    el
  );
}
function App(){
  const [open, setOpen] = useState(false);
  return (
    <div>
      <button onClick={() => setOpen(true)}>Open Modal</button>
      {open && <Modal onClose={() => setOpen(false)}><p>Hello from a Portal!</p></Modal>}
    </div>
  );
}
ReactDOM.createRoot(document.getElementById('try-portal')).render(<App />);

Syntax primer

  • ReactDOM.createPortal(children, container) renders children into a DOM node elsewhere.

Common pitfalls

  • Click events bubble through portals as if children were inside the parent—handle propagation carefully.

Checks for Understanding

  1. Do events inside a portal bubble to ancestors outside the portal container?
  2. Why might you choose a top-level #portal-root element?
Show answers
  1. Yes. Event bubbling follows the React tree, not the DOM containment of the portal.
  2. To centralize overlays/modals above the rest of the app and avoid clipping/stacking issues.

Exercises

  1. Add focus trapping inside the modal and restore focus to the trigger on close.
  2. Make the modal dismissible via Escape key and clicking the backdrop.
  3. Support nested modals; ensure backdrop click only closes the topmost modal.