Learn to use React Portals to break out of the DOM hierarchy for modals, tooltips, and overlays while maintaining full state and accessibility control.
Previously in this course, we explored Managing Global Modals: A Scalable Pattern for React UI, which taught us how to handle modal visibility without prop drilling. While that lesson focused on state architecture, this lesson focuses on the DOM rendering strategy: Portals.
Portals provide a first-class way to render children into a DOM node that exists outside the hierarchy of the parent component. This is critical for UI overlays like modals, dropdowns, and tooltips where CSS properties like overflow: hidden or z-index on a parent container would otherwise clip or obscure your UI.
At its core, a Portal is a way to break the physical DOM structure while keeping the React logical tree intact. When you render a component via createPortal, React keeps the component within the same context and lifecycle as its parent, but the actual DOM elements are moved to a different location in the document body.
This is essential because of how the browser's box model works. If you have a modal defined inside a nested div with position: relative and z-index: 1, that modal is trapped by its parent's stacking context. By using Portals, we can inject that modal directly into document.body, effectively "escaping" the parent's constraints.
To use a portal, we need a target DOM node. We typically create an "overlay root" in our index.html or inject it dynamically on mount.
JSXimport { useEffect, useState } from CE9178">'react'; import { createPortal } from CE9178">'react-dom'; const Portal = ({ children }) => { const [container] = useState(() => document.createElement(CE9178">'div')); useEffect(() => { document.body.appendChild(container); return () => document.body.removeChild(container); }, [container]); return createPortal(children, container); };
This implementation ensures that the container is appended to the body when the component mounts and cleaned up when it unmounts. Because we use useState to initialize the container, the node is stable across re-renders.
One common pitfall when moving elements outside the DOM hierarchy is that keyboard navigation can break. If a user tabs through your application, they expect the focus to stay within the modal. Since the modal is now at the end of the document.body, the browser's native tab flow might jump back to the browser address bar rather than the next element in the modal.
To solve this, you need a focus trap. While libraries like react-focus-lock are industry standards, understanding the principle is key:
Tab key.Tab, shift focus back to the first element.For tooltips or popovers, positioning is rarely static. You often need to calculate the coordinates of a trigger element and position the portal accordingly.
Instead of hardcoding CSS, use the getBoundingClientRect() method on the trigger element. You can then pass these coordinates to your Portal component to apply inline styles or use a library like Floating UI (the evolution of Popper.js) to handle edge cases like screen-edge flipping.
Tooltip component that uses the Portal pattern above.ref to a button that triggers the tooltip.getBoundingClientRect and update the tooltip's absolute position relative to the viewport.overflow: hidden.document.body is the only place to mount. Sometimes, you need multiple portals (e.g., one for global notifications, one for modals). Create a dedicated PortalProvider to manage these mount points.document is not available during the initial render. Ensure your useEffect or useLayoutEffect handles the creation of the portal container only after the component mounts on the client.Portals allow us to transcend the DOM hierarchy without losing the benefits of React’s component model. By carefully managing our mount nodes and implementing robust focus trapping, we can build complex, accessible UI overlays that aren't hampered by the constraints of parent CSS. Remember that while Portals solve the physical placement problem, they don't change how React handles state or events, making them a safe and powerful tool for building high-performance UI components.
Up next: We will dive into Implementing Virtualized Lists to handle rendering massive datasets efficiently.
Master Accessibility (a11y) in React by implementing WAI-ARIA patterns, managing focus traps, and ensuring your complex components are truly inclusive.
Read moreLearn to implement i18n in your React dashboard. We cover managing language state and using context to provide localized UI strings to your components.
Using Portals for UI Overlays
Managing Large-Scale Data Fetching
Micro-Frontends with React
Security Best Practices in React
Advanced Ref Usage
Memoization Pitfalls
Mastering React Patterns for Scalability
Advanced TypeScript with React