ModalPrimitive is a low-level component that provides the basic structure for a modal.


Overview

The Modal Primitive is a foundational abstraction designed to standardize and encapsulate the core logic of modal-like components, such as overlays and dialogs. Instead of focusing on the styling or specific implementation, this primitive provides a flexible base for building custom components such as Modal, Drawer, or other overlay-like UI elements. It includes:

  • ModalPrimitive: The main component that manages the modal's state and behavior.
  • ModalPrimitiveOverlay: The overlay that blocks interaction with the rest of the UI.
  • ModalPrimitiveDialog: The dialog that contains the modal's content.

By leveraging the modal primitive, teams can maintain consistency, reduce redundancy, and streamline the creation of different modal-based components.


Purpose

The modal primitive is designed for:

  1. Component Reusability: Components like Modal and Drawer share similar behavior but differ in appearance and interactions. This primitive consolidates shared logic while allowing tailored styling and behavior.

  2. Separation of Concerns: The primitive isolates core functionality (e.g., focus trapping, dismiss behaviors, animations) from presentation, enabling developers to focus on specific implementations.

  3. Extensibility: By exporting context and internal props, developers can build additional functionality or integrate the modal into complex workflows.

Example

import { Button, Text, ModalPrimitive, ModalPrimitiveOverlay, ModalPrimitiveDialog } from '@customerio/pluma-components/react';
import { useState } from 'react';

export default function Example() {
	const [isOpen, setIsOpen] = useState(false);

	return <>
		<Button variant="primary" onClick={() => setIsOpen(true)}>
		Open Modal
		</Button>
		<ModalPrimitive isOpen={isOpen} onClose={() => setIsOpen(false)}>
			<ModalPrimitiveOverlay>
				<ModalPrimitiveDialog>
					<Text>
						This is the body of the modal.
					</Text>
					<Button onClick={() => setIsOpen(false)}>
						Close
					</Button>
				</ModalPrimitiveDialog>
			</ModalPrimitiveOverlay>
		</ModalPrimitive>
	</>
}

Context

You may want to access the internal context from the modal. For example, if you want to access the onClose function from the dialog, you can use the ModalPrimitiveContext context.

import { ModalPrimitiveContext, ModalPrimitive, ModalPrimitiveOverlay, ModalPrimitiveDialog } from '@customerio/pluma-components/react';

const Dialog = () => {
  const { onClose } = useContext(ModalPrimitiveContext);

  return (
    <Button onClick={onClose}>
      Close
    </Button>
  );
}

const Example = () => {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <>
      <Button onClick={() => setIsOpen(true)}>
        Open Modal
      </Button>

      <ModalPrimitive isOpen={isOpen} onClose={() => setIsOpen(false)}>
        <ModalPrimitiveOverlay>
          <ModalPrimitiveDialog>
            <Dialog />
          </ModalPrimitiveDialog>
        </ModalPrimitiveOverlay>
      </ModalPrimitive>
    </>
  );
}

For more advanced use cases to control the state, we can use ModalPrimitive with the Modal Manager instead.

API

Default:125

The duration (in ms) for the modal animation.

By default, the modal will focus the first focusable element. You can override this by setting the index of the focusable element you want to focus instead.

Whether the modal is open.

Called when the modal is requesting to be closed. For example, when clicking the close button, clicking the overlay, or pressing the Escape key.

Called when the modal finishes closing (after the close animation finishes).

Called when the modal is opened.

Whether onClose should be called when the Escape key is pressed.

Whether onClose should be called when the overlay is clicked.

A special prop containing all the other modal primitive props for use with the ModalsManager.

Whether the overlay should lock scrolling on the body.

On this page