Portal

PreviousNext

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

Portal Target Container:

'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

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

Usage

'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

PropTypeDefaultDescription
childrenReactNodeundefinedThe content to render inside the portal. If no children are provided, nothing will be rendered.
targetstring"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.
disabledbooleanfalseWhen true, renders children in place instead of using a portal. Useful for disabling portal behavior conditionally.
keystringundefinedOptional 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:

    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).