Portals
A portal renders children into a different DOM node:
createPortal(children, domNode). In the React tree they stay where they were,
so context and event bubbling follow the React tree, not the DOM.
Portals are used for modals, tooltips and dropdowns that must escape the
parent's overflow: hidden or stacking context.
Details
createPortalis imported fromreact-domand is returned from a component like any JSX.- Events bubble through the React tree. A click inside a portal reaches
onClickof its React parent, although that parent is not its DOM ancestor. To prevent it, stop the propagation inside the portal. - Context from the React parents is available inside a portal.
- CSS follows the DOM: styles are inherited from the target node, not from the React parent.
- On the server there is no
document. A portal is rendered only on the client, after mount.
Accessibility of Modals
A portal moves only the DOM nodes. A modal also needs:
role="dialog",aria-modal="true"and a label;- focus moved into the modal on open, kept inside while it is open and returned to the trigger on close;
- closing on
Escape.
The native <dialog> element with showModal() does all of this except the
label and renders in the browser's top layer, so a portal is often unnecessary.
Example
The modal is rendered into #modal-root, but a click inside it still bubbles to
the onClick of App:
import { useState } from 'react';
import { createPortal } from 'react-dom';
function Modal({ children }) {
return createPortal(
<div role="dialog" aria-modal="true" aria-label="Example">
{children}
</div>,
document.getElementById('modal-root')
);
}
export default function App() {
const [isOpen, setIsOpen] = useState(false);
return (
<div onClick={() => console.log('Bubbled to App')}>
<button onClick={() => setIsOpen(true)}>Show modal</button>
{isOpen && (
<Modal>
<p>This is a modal</p>
<button onClick={() => setIsOpen(false)}>Close</button>
</Modal>
)}
</div>
);
}
Follow-up Questions
- Why not render the modal in place? A parent with
overflow: hidden,transformor its own stacking context clips it or limits itsz-index. - Does a portal break context or event handling? No, both follow the React tree.
- Where is the content in the DOM? In the target node, usually a sibling of the
app root in
<body>.