Teleport

Use a separate DOM location for elements such as modals, dropdowns, and tooltips when an ancestor’s overflow or stacking context affects their layout.

The <Teleport> component keeps the content in its original component and scope hierarchy while rendering its DOM nodes at the target location, commonly near the end of <body>.

Using Teleport

Wrap the content to move in <Teleport> and set the destination with the to prop.

Import <Teleport> from retend-web, because it is browser-specific.

import { If } from 'retend';
import { Teleport } from 'retend-web';

export function Modal(props) {
  const { isOpen, onClose, title } = props;

  return If(isOpen, {
    true: () => (
      // Teleport this entire chunk of HTML to the #modal-root element
      <Teleport to="#modal-root">
        <div class="modal-overlay" onClick={onClose}>
          <div class="modal-content" onClick--stop={() => {}}>
            <h2>{title}</h2>
            <button type="button" onClick={onClose}>
              Close
            </button>
          </div>
        </div>
      </Teleport>
    ),
  });
}

Ensure that the target element exists in the main HTML file:

<body>
  <!-- Your main app renders here -->
  <div id="app"></div>

  <!-- Your teleported modals will render here -->
  <div id="modal-root"></div>
</body>

The to prop accepts an ID selector, such as #modal-root, or a tag name, such as body.

Logical vs. Physical

<Teleport> changes the physical DOM location of its content.

Logically, the component remains associated with its original position in the component tree:

  1. Reactivity: Cells inside the Teleport continue to update the rendered content.
  2. Scopes: Content teleported to <body> can access data from a Scope Provider around its original parent component.
import { Cell, useScopeContext } from 'retend';
import { Teleport } from 'retend-web';
import { ThemeScope } from './scopes';

function ThemedTooltip() {
  const theme = useScopeContext(ThemeScope);

  const tooltipClass = Cell.derived(() => `tooltip tooltip-${theme}`);

  return (
    <Teleport to="body">
      <div class={tooltipClass}>Helpful information!</div>
    </Teleport>
  );
}

This separates the component’s logical location from the DOM location required by its layout.