Agents (llms.txt)

Portal

A component that renders its children into a new DOM node outside the parent hierarchy.

Portal

Renders children into a DOM node outside the parent hierarchy.

The banner below is portalled into document.body — outside this card — yet declared here in the render tree.
import { useState } from 'react';

import { Portal } from '@/components/portal';
import { Surface } from '@/components/surface';

export const meta = { layout: 'fullscreen' } as const;

export default function PortalExample() {
  const [container, setContainer] = useState<HTMLElement | null>(null);

  return (
    <div className="flex min-h-dvh flex-col items-center justify-center gap-6 p-8">
      <Surface elevation="raised" className="w-full max-w-sm">
        <Surface.Header>
          <Surface.Title>Portal</Surface.Title>
          <Surface.Description>
            Renders children into a DOM node outside the parent hierarchy.
          </Surface.Description>
        </Surface.Header>
        <Surface.Content className="text-foreground-secondary">
          The banner below is portalled into{' '}
          <code className="font-mono text-foreground text-xs">
            document.body
          </code>{' '}
          — outside this card — yet declared here in the render tree.
        </Surface.Content>
      </Surface>

      {/* Custom container — Portal targets this local element. */}
      <Surface
        elevation="sunken"
        className="flex w-full max-w-sm items-center justify-center"
      >
        <div
          ref={setContainer}
          className="grid min-h-20 w-full place-items-center"
        />
        {container ? (
          <Portal container={container}>
            <p className="text-foreground-secondary text-sm">
              Portalled into a local container ref.
            </p>
          </Portal>
        ) : null}
      </Surface>

      {/* Default container — Portal renders into document.body. */}
      <Portal>
        <div
          role="status"
          aria-label="Portalled banner"
          className="fixed inset-x-0 top-0 z-40 bg-foreground px-4 py-2 text-center text-background text-sm"
        >
          This banner is rendered at the end of document.body.
        </div>
      </Portal>
    </div>
  );
}

Dependencies

Source Code

'use client';

import { useLayoutEffect, useState } from 'react';
import { createPortal } from 'react-dom';

type PortalProps = {
  container?: Element;
  children: React.ReactNode;
};

export const Portal = (props: PortalProps) => {
  const [mounted, setMounted] = useState(false);

  useLayoutEffect(() => setMounted(true), []);

  const container = props.container || (mounted && document.body);

  return container ? createPortal(props.children, container) : null;
};

export type { PortalProps };

Features

  • DOM Rendering: Renders content at the end of document.body by default
  • Custom Container: Supports rendering into any DOM element
  • SSR Compatible: Works seamlessly with server-side rendering
  • Type Safe: Full TypeScript support for props and children
  • Lightweight: Uses React’s built-in createPortal API

API Reference

Portal

Prop Default Type Description
container document.body Element The DOM element to render the portal into.
children - React.ReactNode The content to render inside the portal.

Examples

Default

A portal rendering into document.body alongside one targeting a local container.

Portal

Renders children into a DOM node outside the parent hierarchy.

The banner below is portalled into document.body — outside this card — yet declared here in the render tree.
import { useState } from 'react';

import { Portal } from '@/components/portal';
import { Surface } from '@/components/surface';

export const meta = { layout: 'fullscreen' } as const;

export default function PortalExample() {
  const [container, setContainer] = useState<HTMLElement | null>(null);

  return (
    <div className="flex min-h-dvh flex-col items-center justify-center gap-6 p-8">
      <Surface elevation="raised" className="w-full max-w-sm">
        <Surface.Header>
          <Surface.Title>Portal</Surface.Title>
          <Surface.Description>
            Renders children into a DOM node outside the parent hierarchy.
          </Surface.Description>
        </Surface.Header>
        <Surface.Content className="text-foreground-secondary">
          The banner below is portalled into{' '}
          <code className="font-mono text-foreground text-xs">
            document.body
          </code>{' '}
          — outside this card — yet declared here in the render tree.
        </Surface.Content>
      </Surface>

      {/* Custom container — Portal targets this local element. */}
      <Surface
        elevation="sunken"
        className="flex w-full max-w-sm items-center justify-center"
      >
        <div
          ref={setContainer}
          className="grid min-h-20 w-full place-items-center"
        />
        {container ? (
          <Portal container={container}>
            <p className="text-foreground-secondary text-sm">
              Portalled into a local container ref.
            </p>
          </Portal>
        ) : null}
      </Surface>

      {/* Default container — Portal renders into document.body. */}
      <Portal>
        <div
          role="status"
          aria-label="Portalled banner"
          className="fixed inset-x-0 top-0 z-40 bg-foreground px-4 py-2 text-center text-background text-sm"
        >
          This banner is rendered at the end of document.body.
        </div>
      </Portal>
    </div>
  );
}

Basic Usage

A simple example showing how to render content in a portal.


          import { Portal } from "@/foundations/ui/portal/portal";

export default function PortalExample() {
  return (
    <Portal>
      <div className="fixed inset-0 bg-black/50">This content is rendered at the end of document.body</div>
    </Portal>
  );
}
        

Custom Container

Rendering content into a specific DOM element.


          import { Portal } from "@/foundations/ui/portal/portal";
import { useRef } from "react";

export default function CustomContainerExample() {
  const containerRef = useRef<HTMLDivElement>(null);

  return (
    <div>
      <div ref={containerRef} className="relative min-h-[100px]" />
      <Portal container={containerRef.current}>
        <div className="absolute inset-0 flex items-center justify-center">
          This content is rendered inside the container div
        </div>
      </Portal>
    </div>
  );
}
        

Best Practices

  1. Accessibility: When using portals, ensure that:

    • The portal content is properly labeled with ARIA attributes
    • Focus management is handled correctly
    • The content is properly announced to screen readers
  2. Event Bubbling: Remember that events still bubble up through React’s virtual DOM hierarchy, not the actual DOM hierarchy.

  3. SSR Considerations: The portal only renders on the client side. Make sure your code handles the server-side case appropriately.

  4. Z-Index Management: When using multiple portals, carefully manage z-index values to ensure proper stacking order.

Previous

Popover

Next

Progress