← Blog
Code
·Oct 2, 2025·3 min read

A QUICK DIVE INTO REACT PORTALS


Yep, you read that right; Portals. Sounds like something straight out of a blockbuster, right? But here’s the twist: they’ve been sitting in React all along, hiding in plain sight. And honestly? They’re just as cool — maybe even cooler than the movies.

Let’s jump in.

GIF

Why React Portal?

Before we get into the why, let’s first look at the what.

A Portal in React is a way to render elements outside of the normal DOM hierarchy. In other words, a Portal does not follow the rules of its parent containers and its positioning is not tied down by them.

This gives you the ability to create elements that live in their own dedicated DOM node, which helps you avoid messy z-index battles.

Think about modals, drawers, or dropdowns. Many popular UI libraries such as shadcn-ui use Portals to make these components easier to build and maintain.

How To Build A Portal

Imagine a trippy Bill Cipher-style portal ripping open your DOM
Imagine a trippy Bill Cipher-style portal ripping open your DOM

Let’s look at two different implementations of a modal in a parent container with overflow restrictions.

Normally, when you render a modal, it sits inside the component tree where you place it. That means it inherits parent styles like overflow: hidden, positioning, or z-index issues.

// Modal.jsx
export default function Modal({ onClose }) {
  return (
    <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center">
      <div className="bg-white p-4 rounded shadow-lg">
        <h2>This is a Modal</h2>
        <button onClick={onClose}>Close</button>
      </div>
    </div>
  );
}

// App.jsx
import Modal from "./Modal";
import { useState } from "react";

export default function App() {
  const [showModal, setShowModal] = useState(false);

  return (
    <div className="p-10 overflow-hidden">
      <button onClick={() => setShowModal(true)}>Open Modal</button>
      {showModal && <Modal onClose={() => setShowModal(false)} />}
    </div>
  );
}

Here, since the parent container has overflow: hidden, the modal could get clipped or squished and that would not be an ideal scenario since modals mostly work as functional utilities in an application.

This is exactly the kind of mess Portals were made to fix. Instead of fighting with parent containers, we can render the modal into a completely separate DOM node using createPortal.

React gives us createPortal, but dropping it raw into your code can feel clunky or end up being overly repetitive. Instead, let’s wrap it in a small helper component that does the heavy lifting for us.

// Portal.jsx
import { useState, useEffect } from "react";
import { createPortal } from "react-dom";

export default function Portal({ children, container }) {
  const [mounted, setMounted] = useState(false);

  useEffect(() => {
    setMounted(true);
    return () => setMounted(false);
  }, []);

  if (!mounted) return null;

  const target = container || document.body;
  return createPortal(children, target);
}

So what’s going on here?

  • The mounted check makes sure we don’t trip over document in environments like Next.js (where server and client rendering can clash).
  • By default, the Portal drops your content straight into document.body. Some developers also decide to use their own #portal-root container which is also fine.

Now we can wrap the modal content inside <Portal>, keeping it connected to the React tree but rendered outside the normal DOM hierarchy:

// Modal.jsx
import Portal from "./Portal";

export default function Modal({ onClose }) {
  return (
    <Portal>
      <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center">
        <div className="bg-white p-4 rounded shadow-lg">
          <h2>This is a Modal (Portal)</h2>
          <button onClick={onClose}>Close</button>
        </div>
      </div>
    </Portal>
  );
}

And the App stays almost identical:

// App.jsx
import { useState } from "react";
import Modal from "./Modal";
export default function App() {
  const [showModal, setShowModal] = useState(false);
  return (
    <div className="p-10 overflow-hidden">
      <button onClick={() => setShowModal(true)}>Open Modal</button>
      {showModal && <Modal onClose={() => setShowModal(false)} />}
    </div>
  );
}

Once you get comfortable with Portals, you’ll notice they pop up everywhere; not just modals, but dropdowns, tooltips, even notifications.

There you have it: a home-grown Portal that keeps your modals floating above the mess instead of drowning in CSS fights. Think of Portals as React’s version of Houdini teleportation magic. They let your components exist outside the DOM dungeon while still staying wired to the React tree. Whether it’s modals, dropdowns, or tooltips, a reusable <Portal> wrapper means less z-index drama (yeah i know how that feels😔) and more focus on building.

I’d love to hear how you’ve used Portals in your projects or the layout nightmares they’ve rescued you from.


Keep Reading