Rendering children outside the parent DOM hierarchy while keeping React event bubbling.
ReactDOM.createPortal(children, domNode) renders a React subtree into a different DOM node than the one where the parent renders. While the component appears in a different place in the DOM (like a modal at the body level), it remains a child in the React component tree. This means context, events, and React lifecycle work exactly as if the portal were inline.
createPortal(children, document.getElementById('modal-root')) tells React to render children into #modal-root instead of into the parent's DOM subtree. The DOM output is completely separate from the parent's DOM.
Despite rendering elsewhere in the DOM, the portal component is conceptually a child of the component that renders it. It gets the same context values, the same React props, and participates in the same fiber tree.
If you click inside a portal, the click event bubbles through React's component tree to the parent β even though in the DOM, the portal's node is outside the parent's DOM subtree. React's synthetic event system operates on the fiber tree, not the DOM tree.
A portal component can read context values from a Provider that is an ancestor in React's component tree, even if the portal renders into a completely different DOM node.
ReactDOM.createPortal(children, domNode). Renders React children into domNode instead of the parent's DOM container.
Two different hierarchies. The React tree governs context, events, and props. The DOM tree governs visual layout and CSS inheritance.
Portal events bubble through the React component tree (not DOM tree), so parent components receive events from portal children.
Most common portal use case: render modals and tooltips at document.body to avoid overflow:hidden or z-index clipping from ancestor elements.
1import { createPortal } from 'react-dom';23function Modal({ isOpen, onClose, children }) {4 if (!isOpen) return null;56 // Renders into document.body, not into parent's DOM node7 return createPortal(8 <div className="modal-overlay" onClick={onClose}>9 <div className="modal-content" onClick={e => e.stopPropagation()}>10 {children}11 </div>12 </div>,13 document.body // the target DOM node14 );15}1617// Usage β modal is a React child of Card,18// but renders at document.body in the DOM19function Card() {20 const [open, setOpen] = useState(false);21 return (22 <div style={{ overflow: 'hidden' }}> {/* won't clip modal */}23 <button onClick={() => setOpen(true)}>Open Modal</button>24 <Modal isOpen={open} onClose={() => setOpen(false)}>25 Content here!26 </Modal>27 </div>28 );29}
Portals solve a fundamental CSS problem: modals, tooltips, and dropdowns need to visually 'escape' their parent containers. Ancestor CSS like overflow:hidden, z-index stacking contexts, and transform properties can clip or reposition absolutely-positioned children. By rendering at document.body, portals sidestep all of these constraints while preserving React's component model.
Your card component has `overflow: hidden` to clip long text. When you add a dropdown menu or modal inside the card, it gets clipped by the card's overflow β users can't see the full dropdown.
The modal/dropdown is a DOM child of the card, so CSS `overflow: hidden` on the card clips everything inside it. You could remove overflow:hidden but that breaks the text truncation. CSS `z-index` alone can't escape an overflow context.
Use a portal to render the modal at `document.body`. It escapes the card's CSS stacking context entirely. React events still bubble through the React tree (card β modal), so onClick handlers and context providers work normally β only the DOM position changes.
Takeaway: Portals decouple the DOM hierarchy from the React component hierarchy. This is essential for any UI that needs to visually 'break out' of its container: modals, tooltips, dropdowns, toast notifications, and floating menus.
You're building a micro-frontend where your React widget needs to render inside a specific container managed by a host application (not your root div). The host gives you a `#widget-container` div.
Your React app renders into `#app-root` by default. You can't simply move your component tree because it needs to share context and state with other parts of your React app.
Use portals to render specific components into `#widget-container` while keeping them in the same React tree. They still receive context from their React parent, participate in event bubbling, and share state β they just render into a different DOM node.
Takeaway: Portals are the foundation for micro-frontend integration patterns. They let you mount React components into external DOM nodes while preserving the full React component model (context, error boundaries, event delegation).