JGuang

Portal

Portal · Mantine Core

Preview
npm install @mantine/core@9.7.1 @mantine/hooks@9.7.1

Portal

Install: npm install @mantine/core@9.7.1 @mantine/hooks@9.7.1

Version and compatibility

Pinned Mantine Core 9.7.1 requires React and React DOM ^19.2.0 and matching @mantine/hooks@9.7.1, from the installed npm package's peerDependencies. Do not force it into React 18 or another incompatible version. Explain upgrade impact and agree on a plan with the project owner; do not hide conflicts with --force/--legacy-peer-deps. The native Provider enables theme.respectReducedMotion and uses forceColorScheme for light/dark.

Use case

Render children outside their normal DOM parent.

Native props used by this example

These props are extracted from the local typechecked JSX and grouped by their owning native component. This is not an exhaustive prop list; see pinned source signatures and inheritance below.

This minimal example passes children only; no additional props are invented. Optional props are in the source reference.

Interaction and state checks

Current scenarios: Default. Check actual behavior after selecting supported disabled/error/read-only scenarios; components without those native states do not receive invented universal props. Verify keyboard focus and exits, then connect changed values through the native callback signature.

Business integration boundary

The content is rendered outside the normal parent but still inside its iframe document. Portals cannot cross the isolation boundary.

Host setup

Install pinned @mantine/core and @mantine/hooks, import @mantine/core/styles.css at the entry and use MantineProvider. The example's theme/variant are JGuang-authored host props, not universal native component APIs; forceColorScheme synchronizes the app theme. Native implementation source is a reading reference: use npm exports rather than copying internal engines. This batch covers Core, not dates/charts/forms extension packages. Detailed upstream prose remains English.

Runnable usage

import { useState } from 'react';
import * as M from '@mantine/core';
import '@mantine/core/styles.css';


function ComponentPreview({ variant = 'Default' }: { variant?: string }) {
const content = <M.Text>Build something beautiful / 构建美好的界面</M.Text>;
return <M.Portal>{content}</M.Portal>;
}
export default function Example({ variant = 'Default', theme = 'light' }: { variant?: string; theme?: 'light' | 'dark' }) {
return <M.MantineProvider forceColorScheme={theme} theme={{ respectReducedMotion: true }}><ComponentPreview variant={variant} /></M.MantineProvider>;
}

Preview states

Default

The example uses the native Mantine API. For supported input demos, change the local variant to Disabled, Invalid or Read only; these are example labels, not new component props. Keep the documented data, controlled values, stores and compound children when adapting the example.

Usage

Portal is a wrapper component for the ReactDOM.createPortal API. Render any component or element at the end of document.body or at a given element. The Modal and Drawer components are wrapped in Portal by default.

Use Portal to render a component or an element at a different place (defaults to the end of document.body). Portal is useful when you want to prevent parent styles from interfering with children. Usually all these styles are related to position and z-index properties, and portals are used for components with fixed position, for example, modals.

import { useState } from 'react';
import { Portal } from '@mantine/core';

function Demo() {
  const [opened, setOpened] = useState(false);

  return (
    <main style={{ position: 'relative', zIndex: 1 }}>
      {opened && (
        <Portal>
          <div>Your modal content</div>
        </Portal>
      )}

      <button onClick={() => setOpened(true)} type="button">
        Open modal
      </button>
    </main>
  );
}

In the example above, the div element is rendered outside of the parent main (before the closing body tag), but still receives the opened and onClose props. The element will not be affected by the parent z-index.

Reuse target node

By default, Portal reuses the same target node for all instances (reuseTargetNode={true}). To create a new target node for each instance, set reuseTargetNode={false}. In the following example, all three paragraphs will be rendered in separate target nodes:

import { Portal } from '@mantine/core';

function Demo() {
  return (
    <>
      <Portal reuseTargetNode={false}>
        <p>First</p>
      </Portal>

      <Portal reuseTargetNode={false}>
        <p>Second</p>
      </Portal>

      <Portal reuseTargetNode={false}>
        <p>Third</p>
      </Portal>
    </>
  );
}

Specify target DOM node

You can specify the DOM node where the portal will be rendered by passing the target prop:

import { Portal } from '@mantine/core';

const container = document.createElement('div');
document.body.appendChild(container);

function Demo() {
  return <Portal target={container}>My portal</Portal>;
}

Alternatively, you can specify a selector to render the portal in an existing element:

import { Portal } from '@mantine/core';

function Demo() {
  return <Portal target="#portal-container">My portal</Portal>;
}

If you don't specify the target element, a new one will be created and appended to the document.body for each Portal component.

Server side rendering

createPortal is not supported during server-side rendering. All components inside Portal are rendered only after the application was mounted to the DOM.

OptionalPortal component

The OptionalPortal component lets you configure whether children should be rendered in Portal. It accepts the same props as the Portal component:

import { OptionalPortal } from '@mantine/core';

function Demo() {
  return (
    <>
      <OptionalPortal withinPortal>
        This text is rendered in Portal
      </OptionalPortal>
      <OptionalPortal withinPortal={false}>
        This text is rendered as regular child
      </OptionalPortal>
    </>
  );
}

Source-backed parameter reference

Implementation signatures and types are generated from the pinned local source. Optional props, native inherited types, union branches and never exclusions are preserved. A forwardRef implementation's ref is a React reference, not an additional business prop.

Portal(props)
export interface BasePortalProps extends React.ComponentProps<'div'> {
  /**
   * Target element where portal should be rendered. Accepts:
   * - HTMLElement: Renders portal inside this element
   * - string: CSS selector - renders inside first matching element
   * - undefined: Uses shared portal node or creates new one based on `reuseTargetNode`
   *
   * Note: If selector doesn't match any element, portal will not render
   */
  target?: HTMLElement | string;

  /**
   * When true and target is not specified, all Portal instances share a single
   * container node appended to document.body. When false, each Portal creates
   * its own container node.
   *
   * Has no effect when target is specified.
   *
   * @default true
   */
  reuseTargetNode?: boolean;
}

export interface PortalProps extends BasePortalProps {
  /** Content to render inside the portal */
  children: React.ReactNode;
}

export type PortalFactory = Factory<{
  props: PortalProps;
  ref: HTMLDivElement;
}>;

Native and external types

External types remain defined by React or native dependencies, not manually expanded into guessed props. The installed version's TypeScript definitions are authoritative.

Factory<{
  props: PortalProps;
  ref: HTMLDivElement;
}>
React.ComponentProps<'div'>
HTMLElement
React.ReactNode
HTMLDivElement