# A small portal implementation for React Native, with two conditional native integrations

> The @gorhom/portal package is a deliberately simplified portal implementation for React Native and the web, published by Mo Gorhom and MIT licensed. It handles multiple portals and multiple hosts, and it depends on exactly one runtime package, but it also documents two integrations: a FullWindowOverlay from react-native-screens to sit above native views, and a PortalProvider placed under GestureHandlerRootView when using gesture handler.

**gorhom/react-native-portal** — A simplified portal implementation for ⭕️ React Native & Web ⭕️.

- Repository: https://github.com/gorhom/react-native-portal
- Stars: 792 · Forks: 48
- Language: TypeScript
- License: MIT
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/gorhom-react-native-portal

## What a portal does, and what simplified means here

The @gorhom/portal package is described as a simplified portal implementation for React Native and the web, and the README's summary of it is short enough to quote in spirit: you teleport your content to a host layer of the app.

The feature list gives the scope. It handles multiple portals and multiple portal hosts, it allows override functionality, it is compatible with React Native Web, it works with Expo, and it is written in TypeScript with the types included in the package.

That last point is the one to weigh against the word simplified. The library has one runtime dependency, nanoid, and the types are shipped, so there is no separate types package to install and no separate type checking step to configure.

The package is published under the scoped name @gorhom/portal at version 1.0.14, and the release history shows v1.0.12 in January 2022, v1.0.13 in April 2022 and v1.0.14 in June 2022. The last push to the repository was on 2026-09-18, so the tree is being touched, but the published version is the one you will install and it has not moved since 2022. The author is Mo Gorhom, and the package is MIT licensed.

The build tooling is @react-native-community/bob, which is the standard way to publish a React Native library with both CommonJS and module builds.

## Installation and the two pieces of setup

Installation is a single add.

```sh
yarn add @gorhom/portal
```

Then there are two pieces of setup, and both are in the README as separate sections because they are separate concerns.

The first is the provider. You add PortalProvider to your root component, which the README says is usually the App.tsx.

```tsx
export const App = () => (
  <PortalProvider>
  {... your app goes here}
  </PortalProvider>
);
```

The second is the content you want to move. You wrap it in Portal, and it is teleported to the provider layer instead of rendering where it is written.

```tsx
const BasicScreen = () => {
  return (
    { ... }
    <Portal>
      <Text>
        Text to be teleported to the root host
      </Text>
    </Portal>
    { ... }
  );
};
```

That is the whole model. The content is written where it makes sense in the code, and rendered somewhere else in the tree, which is the useful part when a dialog or a toast needs to escape a screen that clips it.

## Custom hosts, and the naming scheme behind them

The simple case sends everything to the root host. When you need a specific destination, you declare a PortalHost with a name at any layer of the app.

```tsx
const CustomView = () => {
  return (
    { ... }
    <PortalHost name="CustomPortalHost" />
    { ... }
  );
};
```

And then you point the portal at that host by name.

```tsx
const BasicScreen = () => {
  return (
    { ... }
    <Portal hostName="CustomPortalHost">
      <Text>
        Text to be teleported to the CustomView component
      </Text>
    </Portal>
    { ... }
  );
};
```

The naming is a string on both sides, which is the classic weakness of this kind of API. A typo in hostName does not fail at build time, it fails at run time, when the content goes nowhere. The library does offer a hook, usePortal, that exposes the internal registry, and the host registration functions registerHost and deregisterHost, which is the escape hatch if you want to verify that a host exists before you portal into it.

The Portal component also accepts a name of its own, and the README says it defaults to an auto generated unique key, which is where nanoid comes in.

## The two native integrations that decide whether it works

This is the part of the README that a React Native developer needs to read carefully, because both requirements are about native views rather than about JavaScript.

The first is React Native Screens. In order to get your teleported content on top of all native views, you need to wrap your content with FullWindowOverlay from react-native-screens.

```tsx
import { FullWindowOverlay } from 'react-native-screens';

const BasicScreen = () => {
  return (
    { ... }
    <Portal>
      <FullWindowOverlay style={StyleSheet.absoluteFill}>
        <Text>
          Text to be teleported to the CustomView component
        </Text>
      </FullWindowOverlay>
    </Portal>
    { ... }
  );
};
```

The second is React Native Gesture Handler. To avoid issues when using the portal with the gesture handler, the PortalProvider must be placed under the GestureHandlerRootView, otherwise the app might freeze.

```tsx
export const App = () => (
  <GestureHandlerRootView>
    <PortalProvider>
    {... your app goes here}
    </PortalProvider>
  </GestureHandlerRootView>
);
```

The README links the issue about the app freezing, which is the kind of link that tells you how often the mistake gets made. The freeze is not a subtle rendering artefact, it is a hang, and the fix is a single component ordering change in the root file.

Both integrations are worth noticing for a second reason. They mean that a project which needs content above native views must also use react-native-screens, and a project using React Native Gesture Handler must order the provider under GestureHandlerRootView.

## The props, the override hooks and the registry

The API surface is documented as a set of props on Portal and PortalHost, and it is worth taking in whole because the two override callbacks are the part most people miss.

Portal takes an optional name, which is the key used as an identifier and defaults to an auto generated unique key. It takes an optional hostName, which is the host to teleport to, defaulting to root. It takes children as ReactNode or an array of them. And it takes handleOnMount and handleOnUnmount, both of which are described as overriding internal mounting and unmounting functionality, useful if you want to trigger an action before the portal content is mounted or unmounted.

```ts
type handleOnMount = (mount?: () => void) => void;
```

```ts
type handleOnUnmount = (unmount?: () => void) => void;
```

The signature is what makes them useful. The callback receives the mount function, so you can run your own action and then call mount, which means the ordering is in your hands rather than fixed.

PortalHost is simpler, with one required prop, a name used as an identifier.

The usePortal hook is the low level access point, and the README documents its shape as taking a host name or key, defaulting to root, and returning registerHost, deregisterHost, addPortal, updatePortal and removePortal, each taking a portal name and, where relevant, the node to render. That is the full registry interface, and it is the right level to reach for if you need to move content imperatively, for example from a callback that does not have the component tree in scope.

## A small build with one dependency and a full example app

The package metadata shows a project with a small surface, which is what you would expect from a library whose entire purpose is to teleport a subtree.

The published entry points cover the usual cases, with main pointing at the CommonJS build, module at the ES module build, types at the TypeScript declarations, and both react-native and source pointing at src/index so that a consumer can consume the TypeScript directly.

The only runtime dependency is nanoid, and the README says the library is built with @react-native-community/bob, the standard community build tool for React Native packages, which is also why the example directory has its own metro and webpack configuration and an app.json for Expo.

The scripts show the workflow. Tests run under jest, there is a typecheck script using tsc with no emit, linting with eslint, and the build runs bob build with the type declaration files copied afterwards and then cleaned up. Releases go through release-it, and a bootstrap script installs the example, the root dependencies and the CocoaPods dependencies for iOS.

The repository also carries the usual furniture of a project with a real process, with a jest.config.js, a babel.config.js, a commitlint configuration for conventional commits, a husky configuration, prettier and eslint configuration, and a release template.

The author asks for sponsorship to keep the library maintained and up to date, and points to his own site and social accounts for private support.

## Where the library is the wrong choice

Three situations rule it out.

The first is a project that needs the full portal model. This one is explicitly simplified, and the simplification shows in the API. Hosts and portals are identified by strings, and the registry is only reachable through the usePortal hook, and there is no built-in notion of portal order, of a host being unmounted while a portal targets it, or of a portal rendering into a host that has not mounted yet. A larger portal library solves those cases, and the cost of choosing the wrong one is debugging behaviour that is hard to reason about.

The second is a project that cannot take the native dependencies. FullWindowOverlay from react-native-screens is needed for the teleported content to appear above native views, and the gesture handler ordering is needed to avoid a freeze. Both are common, but a project with a deliberate choice not to use screens, for example, is a project this library does not fit.

The third is a team that wants a moving dependency. The published version has been 1.0.14 since June 2022, and the repository is touched more recently, so a project that pins to the published version is pinning to code that was current in 2022, with the React Native and React versions of that era in mind.

For an application that needs one overlay to escape one screen, on a project that already uses screens and gesture handler, it is a very small dependency for what it does.

## Conclusion

The @gorhom/portal package fits a React Native application that needs to render content above the current screen, a modal that escapes its parent view, or a shared overlay driven from more than one place, since it is small, typed and works with React Native Web and Expo. It is the wrong choice for a React Native project that needs the full portal semantics of a larger library, because this one is explicitly simplified, and the wrong choice if you cannot add react-native-screens and react-native-gesture-handler to the project, because the overlay and the gesture root are not optional extras. Before you adopt it, check the two native integration steps, place the PortalProvider under GestureHandlerRootView first, and note that the published release is 1.0.14 from 2022, so treat the version as fixed rather than as a moving dependency.

## FAQ

### How do I install and set up the @gorhom/portal package?

Run yarn add @gorhom/portal, add PortalProvider to your root component, usually the App.tsx, and wrap the content you want to teleport in a Portal element. The content is then rendered in the provider layer instead of where it is written.

### How do I teleport content to a custom host in React Native Portal?

Declare a PortalHost with a name at any layer of the app, for example PortalHost name="CustomPortalHost", and then set the hostName prop on the Portal that should go there. The usePortal hook exposes registerHost and deregisterHost if you need to check that a host exists.

### Why does my app freeze when using @gorhom/portal with react-native-gesture-handler?

The PortalProvider must be placed under the GestureHandlerRootView, otherwise the app might freeze. The README links the issue describing the freeze, and the fix is to change the component order in the root file.

### Do I need react-native-screens to use @gorhom/portal?

Yes, for the content to appear on top of all native views. The README says to wrap the portal content with FullWindowOverlay from react-native-screens, using a StyleSheet.absoluteFill style.

### What is the latest version of @gorhom/portal?

The package is published at version 1.0.14, with releases v1.0.12 in January 2022, v1.0.13 in April 2022 and v1.0.14 in June 2022. The last push to the repository was on 2026-09-18, and the library is MIT licensed with types included.

## Sources

- [gorhom/react-native-portal on GitHub](https://github.com/gorhom/react-native-portal)
- [Issues](https://github.com/gorhom/react-native-portal/issues)
- [License: MIT](https://github.com/gorhom/react-native-portal/blob/master/LICENSE)
- [README](https://github.com/gorhom/react-native-portal/blob/master/README.md)
- [Releases](https://github.com/gorhom/react-native-portal/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/gorhom-react-native-portal
