Portals
A portal lets a component render its output into a different part of the DOM — typically a node at the bottom of <body> — while keeping it a normal child of your React tree. It's the standard tool for modals, tooltips, toasts, and dropdowns : anything that must visually escape a parent's overflow , z-index , or transform without losing its place in your component logic.
Learn Portals — Modals & Tooltips in our free React course — a beginner-friendly interactive lesson with runnable examples, a practice exercise and a quick…
Part of the free React course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
What You'll Learn
1️⃣ The Problem Portals Solve
Picture a modal rendered inside a sidebar card that has overflow: hidden . Your full-screen overlay gets clipped to the card's edges . Or a parent has z-index: 1 and your modal can't rise above the header. Portals fix this by moving the DOM output elsewhere:
The demo below models the core idea — a component renders content but the output is placed in a different container:
2️⃣ The createPortal Signature
It's a single function with two arguments: what to render and where to render it.
The domNode must already exist in the page. Most apps add a single <div id="modal-root"> to index.html . Your turn — fill in the target container so the tooltip mounts into "tooltip-root" :
3️⃣ Events Bubble Through the React Tree
Here's the detail that trips people up: even though a portal's DOM node lives somewhere else, React events bubble through the React tree — the place where you wrote the portal, not where the DOM ended up. A click inside a portaled modal still reaches an onClick on the component that rendered it.
The demo walks the React bubble path so you can see handlers fire up the tree even though the DOM node is elsewhere:
Reorder Challenge
These lines of a portal-based Tooltip are scrambled. Put them in the correct order:
Quick Recall
1) Your modal is being clipped by a parent's overflow: hidden . What's the fix?
Render it through a portal into a node at the end of <body> , so it escapes the clipping ancestor entirely.
2) A button inside a portal is clicked. Will an onClick on the component that rendered the portal fire?
Yes. React events bubble through the React tree, not the DOM tree, so the rendering ancestor still hears the click.
3) What are the two arguments to createPortal ?
The JSX to render, and the existing DOM node to render it into: createPortal(children, domNode) .
Common Errors (and the fix)
- Target is null : calling getElementById("modal-root") before that div exists. Add the div to index.html , or create it in an effect.
- Importing from the wrong place: createPortal comes from react-dom , not react .
- Backdrop closes when clicking the modal: stop propagation on the inner box — onClick={e => e.stopPropagation()} — so only backdrop clicks close it.
- Forgetting accessibility: a portaled dialog still needs role="dialog" , focus trapping, and Escape-to-close. The portal only moves the DOM, not the a11y work.
📋 Quick Reference
Task
Syntax
Import
import { createPortal } from "react-dom"
Create a portal
createPortal(<Modal/>, target)
Get the target
document.getElementById("modal-root")
Stop backdrop close
onClick={e => e.stopPropagation()}
Mount point (HTML)
<div id="modal-root"></div>
Frequently Asked Questions
Mini-Challenge: Toast Notification
Toasts almost always live in a portal so they float above everything. Model a toast that mounts a message into the "toast-root" container with a level icon — "success" → "✅".
🎉 Lesson Complete
- ✅ createPortal(jsx, domNode) renders output into another DOM node
- ✅ Portals free modals/tooltips from overflow , z-index , and transform traps
- ✅ Events bubble through the React tree , not the DOM tree
- ✅ Context and state work normally inside a portal
- ✅ You still own accessibility: focus trap, Escape, ARIA roles
- ✅ Next lesson: Checkpoint — build a real app combining the hooks you've learned
Practice quiz
Which function creates a React portal?
- ReactDOM.render
- usePortal
- createPortal
- renderInto
Answer: createPortal. createPortal(children, domNode) renders the children into a different DOM node.
Where is createPortal imported from?
- react-dom
- react
- react-router-dom
- react/portals
Answer: react-dom. createPortal comes from react-dom, not react.
What are the two main arguments to createPortal?
- A ref and a callback
- A component and its props
- A selector string and an event
- The JSX/children and the target DOM node
Answer: The JSX/children and the target DOM node. createPortal(children, domNode) takes the JSX to render and an existing DOM node to render it into.
A click inside a portal bubbles through which tree?
- The DOM tree only
- The React tree (where the portal was rendered)
- Neither — events don't bubble from portals
- The CSS cascade
Answer: The React tree (where the portal was rendered). React events bubble through the React tree, so the rendering ancestor still hears the click even though the DOM node is elsewhere.
What problem do portals primarily solve for modals?
- They escape parent overflow, z-index, and transform clipping
- They make state global
- They prevent re-renders
- They add accessibility automatically
Answer: They escape parent overflow, z-index, and transform clipping. A portal lifts the DOM output out of clipping ancestors so the overlay can cover the screen.
The target DOM node passed to createPortal must…
- Be created inside the component's JSX
- Be a React component
- Already exist in the page (e.g. a div in index.html)
- Be the document body only
Answer: Already exist in the page (e.g. a div in index.html). The domNode must already exist; most apps add a <div id="modal-root"> to index.html.
Do Context and state still work inside a portal?
- No, the portal is isolated from the React tree
- Yes, the portal is a normal child in the React tree
- Only state works, not Context
- Only if you pass them as props
Answer: Yes, the portal is a normal child in the React tree. A portal is a real child in the React tree, so it inherits everything its React ancestors provide.
If getElementById returns null when calling createPortal, the likely cause is…
- The component is memoized
- createPortal was imported from react
- The portal has no key
- The target div doesn't exist yet in the DOM
Answer: The target div doesn't exist yet in the DOM. Calling getElementById before the target div exists returns null; add it to index.html or create it first.
To stop a backdrop click from closing a modal when clicking inside it, you…
- Remove the onClick
- Call e.stopPropagation() on the inner box
- Use a portal
- Set z-index to 0
Answer: Call e.stopPropagation() on the inner box. stopPropagation on the inner box prevents the click from reaching the backdrop's close handler.
Which is true about a portaled dialog and accessibility?
- The portal handles focus trapping for you
- Portals make dialogs inaccessible
- You still own focus trap, Escape-to-close, and ARIA roles
- ARIA roles are added automatically
Answer: You still own focus trap, Escape-to-close, and ARIA roles. The portal only moves the DOM; you still need role="dialog", focus trapping, and Escape-to-close.
Continue this course
- Previous: Component Composition & children
- Next: Checkpoint: Build with Hooks