Componentsv2.189.0
Iconsv1.19.0
MCPv0.8.61
Tokensv0.34.3

prefer-managed-modals

Github

Prefer managed modals (useModals/useModal) over plain isOpen-controlled Modal usage.


Use this rule to steer Modal usage towards the Modal Manager.

Controlling a Modal directly with isOpen means every call site keeps its own open-state boilerplate, and the same modal is hard to reuse across the app. The modal manager (useModals/useModal) owns the open state, handles nesting, and lets callers await the close result. Define a managed modal component that forwards modalState into the Modal's state prop, and open it through the manager.

Incorrect

import { Modal, ModalBody } from '@customerio/pluma-components/react';
import { useState } from 'react';

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

	return (
		<Modal isOpen={isOpen} onClose={() => setIsOpen(false)} title="Example">
			<ModalBody>Content</ModalBody>
		</Modal>
	);
}

Correct

import { Modal, ModalBody } from '@customerio/pluma-components/react';
import type { PlumaManagedModalProps } from '@customerio/pluma-components/react';

export function ExampleModal(props: PlumaManagedModalProps) {
	const { modalState } = props;

	return (
		<Modal state={modalState} title="Example">
			<ModalBody>Content</ModalBody>
		</Modal>
	);
}

What is checked

The rule reports Pluma Modal/PlumaModal bindings that receive an isOpen (or @isOpen) prop. It resolves import aliases before matching, so a renamed Pluma Modal is still reported, and a local component named Modal is ignored.

Managed modals never pass isOpen — the manager controls the open state through the state prop — so any isOpen usage signals a hand-rolled modal.

If a call site genuinely cannot go through the modal manager, disable the rule locally with an eslint-disable-next-line comment explaining why.

On this page