Skip to Content
DocsModal system

The modal system

The modal system is a part of the framework that handles rendering modal dialogs, warnings, and other similar displays. Its main goal is to coordinate modals across different applications so that the topmost modal is visible while any previously opened modals remain mounted but hidden in the stack.

To use the modal system, first your application must register the modal as described below. Once the modal is registered, call showModal with the modal name and the framework will render the modal for you.

Usage

Implementation of the modal

The ModalHeader and ModalBody should be at the top level of the modal component and wrapped in a React.Fragment.

import { Button, ModalBody, ModalFooter, ModalHeader } from '@carbon/react'; interface DeleteItemModalProps { close: () => void; itemName: string; } export default function DeleteItemModal({ close, itemName }: DeleteItemModalProps) { return ( <> <ModalHeader closeModal={close} title="Delete item" /> <ModalBody> <p>Are you sure you want to delete {itemName}?</p> </ModalBody> <ModalFooter> <Button kind="secondary" onClick={close}> Cancel </Button> <Button kind="danger">Delete</Button> </ModalFooter> </> ); }

Failing to wrap your modal in a React Fragment would cause the modal body to not vertical-scroll properly.

Registering the modal

Register the modal by name in the modals property in routes.json and export it in the index.ts file of the app that defines the modal. The name should be suffixed with -modal. For example, if the modal name is delete-condition, the name should be delete-condition-modal. The component should be the name of the component that renders the modal.

routes.json
"modals": [ { "name": "your-modal-name-modal", "component": "modalComponentName" } ]
index.ts
import { getAsyncLifecycle } from '@openmrs/esm-framework'; export const modalComponentName = getAsyncLifecycle( () => import('./path-to-the-modal-component/modal-component'), options, );

Older code may still register modal components as extensions and launch them by extension name. The framework currently falls back to that registration path and logs a deprecation warning, but new modals should always use the modals section in routes.json.

Triggering the modal

Finally, you can trigger your modal by calling showModal along with all the props you need to pass into the modal. The function returned by showModal can be called to “dispose” or force close the modal. The framework also passes that same function to the modal as the close prop.

const close = showModal('your-modal-name-modal', { itemName: 'Example item', // other props to pass in to the modal component });

Pass the injected close prop to Carbon’s ModalHeader as closeModal={close}.

Important considerations

The modal system supports stacking multiple modals. When a new modal is opened while another is already displayed, the new modal appears on top while the previous one remains in the stack (hidden). Closing the top modal reveals the one beneath it. This allows for nested modal workflows without losing context.

The onClose callback

The showModal function accepts an optional third parameter onClose callback that is called when the modal is closed:

const dispose = showModal('your-modal-name-modal', { itemName: 'Example item', // other props }, () => { // This callback is called when the modal is closed console.log('Modal was closed'); });

You can control the modal size by passing a size prop. Available sizes are 'xs', 'sm', 'md' (default), and 'lg':

const dispose = showModal('your-modal-name-modal', { itemName: 'Example item', size: 'sm', // or 'xs', 'md', 'lg' });

Error handling

If you attempt to show a modal that hasn’t been registered, the framework will report an error to the console but won’t throw an exception. Make sure your modal is properly registered in routes.json before calling showModal.

Automatic behaviors

The modal system automatically handles several behaviors for you:

  • Body scroll locking: When a modal is open, the page body scroll is automatically locked to prevent background scrolling
  • ESC key handling: Pressing the ESC key closes the topmost modal
  • Stack visibility: Only the topmost modal is visible; previously opened modals remain mounted but hidden until the top one closes

The close prop

The framework automatically adds a close prop to your modal component. This is the dispose function returned by showModal. Use this prop to close the modal from inside the modal component, and pass it to Carbon’s header as closeModal={close}.

You may still see older code pass a custom closeModal prop through showModal. That works, but it is unnecessary unless an existing component API explicitly expects a prop with that name.

Last updated on