Home
React
Advanced Hook: createPortal()
Daniel Nguyen
Daniel Nguyen
July 18, 2025
1 min

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.

1. Why Do We Need Portals?

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.

2. Example: Modal

import { createPortal } from 'react-dom'
type ModalProps = {
title: string
content: string
handleClose: () => void
}
function Modal({ title, content, handleClose }: ModalProps) {
return createPortal(
<div className="modal-backdrop">
<div
className="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 && (
<Modal
title="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.

3. Example: Tooltip

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: string
position: { top: number; left: number }
}
function Tooltip({ text, position }: TooltipProps) {
return createPortal(
<div
role="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.

4. How Do Events Work Through Portals?

Although a portal changes where elements appear in the DOM, React still treats them as part of the original React tree.

This means:

  • State: Component state is preserved while the component remains mounted.
  • Context: Context values remain available from React ancestors.
  • Events: React events can bubble to React ancestors, even when those ancestors are not DOM ancestors.

For example, clicking inside a portal may trigger an onClick handler on a React parent outside the portal’s DOM container.


Tags

#ReactAdvancedHooks

Share

Daniel Nguyen

Daniel Nguyen

Frontend Developer

Frontend developer specializing in React, Next.js, and JavaScript. Writing practical guides on modern web development at Dev98.

Expertise

React
Next.js
JavaScript
TypeScript
Python

Social Media

githublinkedinyoutubewebsite

Related Posts

Advanced Hook
Advanced Hook: useSyncExternalStore()
July 19, 2025
1 min
Dev98

Dev98

React · Next.js · Web development