Modals
Modals are dialog boxes that appear on top of the current page, typically used for confirmations, quick actions, or focused interactions that require user attention.
-
Modal components should use the
.modal.tsxsuffix to clearly indicate their purpose and enable proper translation key extraction. -
Use Carbon’s
Modalcomponents (ModalHeader,ModalBody,ModalFooter) for consistent modal structure. KeepModalHeaderandModalBodyat the top level of the modal component, wrapped in a fragment, so the shell can size and scroll the modal body correctly:import { ModalHeader, ModalBody, ModalFooter } from '@carbon/react'; function DeleteModal({ close, itemId }: DeleteModalProps) { return ( <> <ModalHeader closeModal={close} title={t('deleteItem', 'Delete item')} /> <ModalBody> <p>{t('deleteConfirmation', 'Are you sure you want to delete this item?')}</p> </ModalBody> <ModalFooter> <Button kind="secondary" onClick={close}> {t('cancel', 'Cancel')} </Button> <Button kind="danger" onClick={handleDelete}> {t('delete', 'Delete')} </Button> </ModalFooter> </> ); } -
Use loading states in modal action buttons to provide feedback during async operations:
const [isDeleting, setIsDeleting] = useState(false); <Button kind="danger" onClick={handleDelete} disabled={isDeleting}> {isDeleting ? ( <InlineLoading description={t('deleting', 'Deleting') + '...'} /> ) : ( <span>{t('delete', 'Delete')}</span> )} </Button> -
Modals should handle errors gracefully and display them using snackbars. Prefer
async/awaitover promise chains for better readability:const handleDelete = useCallback(async () => { setIsDeleting(true); try { await deleteItem(itemId); await mutate(); // Update SWR cache close(); showSnackbar({ isLowContrast: true, kind: 'success', title: t('itemDeleted', 'Item deleted'), }); } catch (error) { showSnackbar({ kind: 'error', title: t('errorDeletingItem', 'Error deleting item'), subtitle: error instanceof Error ? error.message : t('unknownError', 'Unknown error'), }); } finally { setIsDeleting(false); } }, [itemId, close, mutate, t]); -
Use
showModalfrom@openmrs/esm-frameworkto launch modals programmatically. The framework injects acloseprop into the modal and also returns the same close function to the caller:import { showModal } from '@openmrs/esm-framework'; const handleOpenModal = () => { showModal('delete-item-modal', { itemId: '123', }); }; -
Export modal components with
getAsyncLifecyclefor code splitting, then register them in themodalssection ofroutes.json. TheshowModalname must match thenamevalue inroutes.json, andcomponentmust match the exported lifecycle name:export const deleteItemModal = getAsyncLifecycle( () => import('./delete-item.modal'), options );{ "modals": [ { "name": "delete-item-modal", "component": "deleteItemModal" } ] } -
Use appropriate button kinds for modal actions:
dangerfor destructive actions (delete, remove)primaryfor primary confirmationssecondaryfor cancel actions
<ModalFooter> <Button kind="secondary" onClick={close}> {t('cancel', 'Cancel')} </Button> <Button kind="danger" onClick={handleDelete}> {t('delete', 'Delete')} </Button> </ModalFooter>