---
title: Portal
description: A component that renders children into a DOM node that exists outside the parent component's DOM hierarchy, useful for modals, tooltips, and overlays.
---

```tsx
'use client';

import { Portal } from '@/ui/Portal';
import { useState } from 'react';

export function PortalDemo() {
  const [showPortal, setShowPortal] = useState(false);

  return (
    <div className="space-y-16">
      <button
        onClick={() => setShowPortal(!showPortal)}
        className="rounded-md bg-blue-600 px-16 py-8 text-white hover:bg-blue-700"
      >
        {showPortal ? 'Hide' : 'Show'} Portal Content
      </button>

      {showPortal && (
        <Portal target="portal-demo-target">
          <div className="rounded-md border border-gray-300 bg-white p-16 shadow-lg">
            <p className="text-gray-900">
              This content is rendered in a portal outside the normal DOM hierarchy.
            </p>
          </div>
        </Portal>
      )}

      <div
        id="portal-demo-target"
        className="min-h-64 rounded-md border-2 border-dashed border-gray-400 p-16"
      >
        <p className="mb-8 text-sm text-gray-600">Portal Target Container:</p>
      </div>
    </div>
  );
}
```

## Installation

Using the CLI:

```bash
npx shadcn@latest add https://develop.trident-ui.pro.clubmed/r/portal.json
```

Or manually, copy the following code into `/ui/portal.tsx`:

```tsx
import {
  Children,
  type FunctionComponent,
  type PropsWithChildren,
  useEffect,
  useState,
} from 'react';
import { createPortal } from 'react-dom';

const createDivElementWithId = (id: string) => {
  /* c8 ignore next 4 */
  if (typeof document === 'undefined') {
    console.warn('createDivElementWithId() can not be run outside of a browser');
    return null;
  }

  const element = document.createElement('div');
  element.id = id;
  element.classList.add('relative', 'isolate', 'z-1');
  document.body.append(element);

  return element;
};

export interface PortalProps {
  key?: string;
  target?: string;
  disabled?: boolean;
}

export function Portal({
  children,
  target = 'portal',
  disabled = false,
  key,
}: PropsWithChildren<PortalProps>) {
  const [isClient, setIsClient] = useState(false);
  const [portalContainer, setPortalContainer] = useState<Element | null>(null);

  useEffect(() => {
    setIsClient(true);

    if (portalContainer === null) {
      const container = document.getElementById(target) || createDivElementWithId(target);

      if (container) {
        setPortalContainer(container);
      }
    }
  }, [target]);

  if (!isClient) {
    return null;
  }

  if (disabled) {
    return children;
  }

  return Children.count(children) && portalContainer
    ? createPortal(children, portalContainer, key)
    : null;
}

/**
 * Wrap a component onto a portal
 */
export function withPortal<Props extends PropsWithChildren>(
  target: string,
  Cmp: FunctionComponent<Props>,
) {
  return (props: Props) => (
    <Portal target={target}>
      <Cmp {...props} />
    </Portal>
  );
}
```

## Usage

```tsx showLineNumbers
'use client';

import { Portal } from '@/docs/lib/ui/portal';

export function Component() {
  return (
    <>
      <Portal target="modal-root">
        <div className="modal-content">This will be rendered in the #modal-root element</div>
      </Portal>

      <div id="modal-root" />
    </>
  );
}
```

## API Reference

### Portal

| Prop       | Type        | Default     | Description                                                                                                                          |
| ---------- | ----------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `children` | `ReactNode` | `undefined` | The content to render inside the portal. If no children are provided, nothing will be rendered.                                      |
| `target`   | `string`    | `"portal"`  | The ID of the DOM element where the portal content will be rendered. If the element doesn't exist, it will be created automatically. |
| `disabled` | `boolean`   | `false`     | When `true`, renders children in place instead of using a portal. Useful for disabling portal behavior conditionally.                |
| `key`      | `string`    | `undefined` | Optional React key for the portal. Passed directly to `createPortal`.                                                                |

## Notes

- **React Portal**: Uses React's `createPortal` API to render content outside the parent component's DOM hierarchy while maintaining the React component tree.

- **Automatic Container Creation**: If the target element with the specified ID doesn't exist, the Portal component automatically creates a new `<div>` with that ID and appends it to `document.body`. The created element includes default classes: `relative`, `isolate`, and `z-1`.

- **Client-Side Only**: The Portal component is a client component (`"use client"`) and only renders on the client side. It returns `null` during server-side rendering to prevent hydration mismatches.

- **Conditional Rendering**: The portal only renders when:

  - The component has mounted on the client (`isClient` is `true`)
  - Children are provided (`Children.count(children) > 0`)
  - A valid portal container exists
  - `disabled` prop is `false`

- **Disabled Mode**: When `disabled={true}`, the Portal component renders children directly in place without using a portal. This is useful for conditionally enabling/disabling portal behavior based on breakpoints or other conditions.

- **Use Cases**: Portals are commonly used for:

  - Modal dialogs that need to escape parent overflow/z-index constraints
  - Tooltips and popovers that need to be positioned relative to the viewport
  - Dropdown menus that might be clipped by parent containers
  - Toast notifications that need to appear at the root level

- **withPortal HOC**: The component exports a `withPortal` higher-order component that wraps any component with Portal functionality:

  ```tsx
  const ModalWithPortal = withPortal('modal-root', MyModalComponent);
  ```

- **Z-Index Management**: Portal containers created automatically have `z-1` class applied, providing a base stacking context. You can control the z-index of portal content through className on the children.

- **Multiple Portals**: You can render multiple Portal components with different `target` IDs to create separate portal roots for different types of content (e.g., modals, tooltips, notifications).
