Skip to Content

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.tsx suffix to clearly indicate their purpose and enable proper translation key extraction.

  • Use Carbon’s Modal components (ModalHeader, ModalBody, ModalFooter) for consistent modal structure. Keep ModalHeader and ModalBody at 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/await over 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 showModal from @openmrs/esm-framework to launch modals programmatically. The framework injects a close prop 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 getAsyncLifecycle for code splitting, then register them in the modals section of routes.json. The showModal name must match the name value in routes.json, and component must 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:

    • danger for destructive actions (delete, remove)
    • primary for primary confirmations
    • secondary for cancel actions
    <ModalFooter> <Button kind="secondary" onClick={close}> {t('cancel', 'Cancel')} </Button> <Button kind="danger" onClick={handleDelete}> {t('delete', 'Delete')} </Button> </ModalFooter>
Last updated on