React Portals let you render JSX into a different DOM node while keeping the component logically inside the same React tree.
import { createPortal } from 'react-dom'createPortal(children, domNode)
children: The JSX to render.domNode: The target DOM node where the content will be mounted.Some UI elements, such as modals, dropdowns, and tooltips, can be affected by their ancestors’ CSS.
For example, a parent with overflow: hidden may clip a tooltip. Properties such as transform can also create containing blocks that affect the positioning of fixed elements.
A portal lets you render the UI elsewhere in the DOM, often directly inside document.body.
import { createPortal } from 'react-dom'type ModalProps = {title: stringcontent: stringhandleClose: () => void}function Modal({ title, content, handleClose }: ModalProps) {return createPortal(<div className="modal-backdrop"><divclassName="modal"role="dialog"aria-modal="true"aria-labelledby="modal-title"><h2 id="modal-title">{title}</h2><p>{content}</p><button onClick={handleClose}>Close</button></div></div>,document.body)}
Usage:
function App() {const [showModal, setShowModal] = React.useState(false)return (<><button onClick={() => setShowModal(true)}>Show Modal</button>{showModal && (<Modaltitle="My Modal"content="This is the modal content."handleClose={() => setShowModal(false)}/>)}</>)}
This illustrates portal rendering. A production modal should also manage keyboard interaction, focus trapping, focus restoration, and closing with Escape where appropriate.
For server-rendered applications, ensure the portal is only created on the client because document.body is unavailable during server rendering.
A tooltip inside a card may be clipped by overflow: hidden.
A portal can render it outside the card:
import { createPortal } from 'react-dom'type TooltipProps = {text: stringposition: { top: number; left: number }}function Tooltip({ text, position }: TooltipProps) {return createPortal(<divrole="tooltip"className="tooltip"style={{position: 'fixed',top: position.top,left: position.left,}}>{text}</div>,document.body)}
The trigger’s position can be calculated using getBoundingClientRect().
Remember that the positioning strategy matters: getBoundingClientRect() returns viewport-relative coordinates, which pair naturally with position: fixed. For position: absolute relative to the document, scroll offsets must be considered.
A robust tooltip must also account for scrolling, resizing, viewport boundaries, and keyboard accessibility.
Although a portal changes where elements appear in the DOM, React still treats them as part of the original React tree.
This means:
For example, clicking inside a portal may trigger an onClick handler on a React parent outside the portal’s DOM container.