React components are meant to be reusable and generic, but HTML doesn't always play so nice. The same tooltip component might work fine in one place, but be obstructed in another:


For fixed floating components—tooltips, dropdowns, modals—this is a big problem! We want these components to work wherever we render them.

Thankfully, React has a solution for this: Portals. In essence, this API lets a component render in a different part of the DOM tree.

The API is a bit funky though, especially when server-rendering or pre-generating (like with Gatsby or Next); portals can't be hydrated. This has led to a lot of confusion.

This component wraps over all of the confusing bits, and provides an easy-to-use reusable abstraction.

First, we need to create our container. I do this directly in the HTML:


Then, whenever you need to render something into a portal, you use it like so:


If you already have a useHasMounted hook, you can use that here as well.

You may be thinking that it would be better for the component to create the portal's target node and append it to the body. This way, you wouldn't need to modify the HTML file.

I'm personally not a fan of this idea, for a couple reasons:

  • Sometimes the order of these nodes matter. If you have a portal for a tooltip, and a portal for a modal, you probably want the tooltip to be above the modal, since a modal could have a tooltip within it.
  • I believe that there's a contract with react components: they will only ever modify the DOM within its control (with portals being the escape hatch). Having a component create a node and appended to the body feels really icky to me.

That said, this is definitely a tradeoff. You might have a different opinion, and that's alright! Feel free to modify it; that's the main reason I share snippets, instead of publishing NPM packages.

One issue with using portals generally is that focus becomes unintuitive; for folks who navigate with the keyboard, you'll want to make sure that the content in the portal can be reached.

The exact solution depends on what you're building, so it's outside the scope of this snippet. But be mindful! Make sure that your application is usable without a mouse.