
React normally renders a component inside its parent DOM tree. That works for most UI, but overlays such as modals, drawers, dropdowns, tooltips, and toasts often need to escape local layout constraints. A parent may have overflow: hidden, a new stacking context, or a layout boundary that clips the overlay.
React Portal lets the component stay in the React tree while its DOM is mounted somewhere else, usually under document.body.
What Portal solves
Portal is useful when:
- A parent clips the overlay with
overflow: hidden. - A parent creates a stacking context and
z-indexno longer wins. - A component inside a card needs to show a page-level modal.
- A toast or command palette should live at the app shell level.
Portal separates ownership from DOM placement. State, Context, and React event behavior still follow the React component tree, but the actual DOM node can be outside the parent container.
Basic usage
import { createPortal } from "react-dom"
function Modal({ open, children }: { open: boolean; children: React.ReactNode }) {
if (!open) return null
return createPortal(
<div className="modal-backdrop">
<div className="modal-panel">{children}</div>
</div>,
document.body,
)
}
Now Modal can be used inside any component, while the DOM is rendered under body.
When to use Portal
Good fits:
- Global modal dialogs
- Drawers and sheets
- Toast notifications
- Tooltips
- Dropdown menus that escape clipping
- Command palettes
Not every popup needs Portal. If a menu belongs inside a small local area and is not clipped, a normal component may be simpler.
Next.js and browser-only APIs
In Next.js, code that touches document.body must run in a Client Component or after the browser is available. A Portal component usually starts with "use client" because it needs browser DOM access and often handles events.
"use client"
import { createPortal } from "react-dom"
Keep the Portal boundary small. Do not make the entire page a Client Component just because the modal needs one.
Accessibility matters
Portal fixes DOM placement. It does not automatically make a modal accessible.
A modal should usually:
- Move focus into the dialog when opened.
- Restore focus when closed.
- Support
Escapeto close when appropriate. - Provide an accessible title.
- Prevent background content from being read or interacted with.
- Lock background scroll for full-screen overlays.
For production UI, consider a well-tested dialog primitive rather than hand-rolling every focus behavior.
Portal and z-index
If a tooltip with z-index: 9999 is still hidden, the issue may be stacking context, not the number. Moving the overlay to a top-level Portal can change the comparison boundary.
For the CSS side, read Why z-index Fails: Understanding CSS Stacking Context.
Related FreeMac guides
- For component abstraction, read React Component Design: Composition, Abstraction, and Reuse.
- For Next.js boundaries, read Next.js Server vs Client Components.
- For CSS layers, read Why z-index Fails: Understanding CSS Stacking Context.
Continue reading
React Hooks Guide: State, Effects, Context, and Reducers
Use React Hooks by responsibility: useState for local state, useEffect for external synchronization, useReducer for complex transitions, and useContext for shared values.
12 min readSide Effects in Vue 3 and React: Watchers, Effects, and Cleanup
Compare side effect handling in Vue 3 and React, including API requests, event listeners, timers, AbortController, watchers, useEffect dependencies, and cleanup.
9 minReact Hydration Failed: Causes, Debugging, and Fixes
Debug server and client rendering mismatches caused by dates, random values, browser APIs, invalid HTML, local storage, locale differences, and DOM-changing extensions.
Subscribe to FreeMac
Weekly picks: free Mac software reviews, trusted source updates, alternatives, and low-friction guides.