Modal Components
Proton provides a comprehensive modal system (ModalTwo) for displaying dialogs, confirmations, and overlay content.
Modal
The main modal component with animation and accessibility features.
Location: components/modalTwo/Modal.tsx
Basic Usage
import { useState } from 'react';
import { Button } from '@proton/atoms/Button/Button';
import Modal from '@proton/components/components/modalTwo/Modal';
import ModalHeader from '@proton/components/components/modalTwo/ModalHeader';
import ModalContent from '@proton/components/components/modalTwo/ModalContent';
import ModalFooter from '@proton/components/components/modalTwo/ModalFooter';
const MyComponent = () => {
const [open, setOpen] = useState(false);
return (
<>
<Button onClick={() => setOpen(true)}>Open Modal</Button>
<Modal open={open} onClose={() => setOpen(false)} size="small">
<ModalHeader title="My Modal" />
<ModalContent>
<p>Modal content goes here</p>
</ModalContent>
<ModalFooter>
<Button onClick={() => setOpen(false)} color="weak">Cancel</Button>
<Button onClick={() => setOpen(false)} color="norm">Confirm</Button>
</ModalFooter>
</Modal>
</>
);
};
Props
Whether the modal is open or not
Callback when user clicks close button or presses Escape key
Modal size. Options: xsmall, small, medium, large, xlarge, full. Default: medium
Make modal fullscreen on mobile devices only
Disables closing the modal by pressing the Escape key
enableCloseWhenClickOutside
Allow closing modal by clicking outside of it
Callback when modal has finished its enter animation
Callback when modal has finished its exit animation
Callback when user clicks on the backdrop outside the dialog
Whether or not to blur the backdrop of the modal
Whether the modal should render behind the backdrop
Optional id to overwrite the internally generated id (used for accessibility)
Additional classname on the root element
Extra hotkeys definition for modals
Examples
Basic Modal
Size Variants
Confirmation Dialog
Form Modal
<Modal open={open} onClose={() => setOpen(false)}>
<ModalHeader title="Basic Modal" />
<ModalContent>
<p>This is a basic modal dialog.</p>
</ModalContent>
<ModalFooter>
<Button onClick={() => setOpen(false)}>Close</Button>
</ModalFooter>
</Modal>
{/* Extra small */}
<Modal open={open} onClose={onClose} size="xsmall">
<ModalContent>XSmall modal</ModalContent>
</Modal>
{/* Small */}
<Modal open={open} onClose={onClose} size="small">
<ModalContent>Small modal</ModalContent>
</Modal>
{/* Large */}
<Modal open={open} onClose={onClose} size="large">
<ModalContent>Large modal</ModalContent>
</Modal>
{/* Fullscreen */}
<Modal open={open} onClose={onClose} fullscreen>
<ModalContent>Fullscreen modal</ModalContent>
</Modal>
<Modal
open={open}
onClose={() => setOpen(false)}
size="small"
>
<ModalHeader title="Confirm Action" />
<ModalContent>
<p>Are you sure you want to delete this item?</p>
</ModalContent>
<ModalFooter>
<Button onClick={() => setOpen(false)} color="weak">
Cancel
</Button>
<Button onClick={handleDelete} color="danger">
Delete
</Button>
</ModalFooter>
</Modal>
<Modal open={open} onClose={() => setOpen(false)}>
<ModalHeader title="Edit Profile" />
<ModalContent>
<Form onSubmit={handleSubmit}>
<Input
label="Name"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<Input
label="Email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</Form>
</ModalContent>
<ModalFooter>
<Button onClick={() => setOpen(false)} color="weak">
Cancel
</Button>
<Button onClick={handleSubmit} color="norm">
Save
</Button>
</ModalFooter>
</Modal>
Header component for modals with title and close button.
Location: components/modalTwo/ModalHeader.tsx
Usage
import ModalHeader from '@proton/components/components/modalTwo/ModalHeader';
<ModalHeader
title="My Modal Title"
subline="Optional subtitle"
actions={<Button size="small">Action</Button>}
/>
Props
Optional subtitle or description
Optional action buttons to display in the header
Whether to show the close button. Default: true
Whether to display the title. Default: true
ModalContent
Content wrapper for modal body.
Location: components/modalTwo/ModalContent.tsx
Usage
import ModalContent from '@proton/components/components/modalTwo/ModalContent';
<ModalContent>
<p>Your modal content here</p>
</ModalContent>
Footer component for modal actions.
Location: components/modalTwo/ModalFooter.tsx
Usage
import ModalFooter from '@proton/components/components/modalTwo/ModalFooter';
<ModalFooter>
<Button color="weak">Cancel</Button>
<Button color="norm">Confirm</Button>
</ModalFooter>
useModalState
Hook for managing modal state.
Location: components/modalTwo/useModalState.ts
Usage
import useModalState from '@proton/components/components/modalTwo/useModalState';
const MyComponent = () => {
const [modalState, setModalOpen, setModalClosed] = useModalState();
return (
<>
<Button onClick={setModalOpen}>Open Modal</Button>
<Modal
open={modalState}
onClose={setModalClosed}
>
<ModalContent>Modal content</ModalContent>
</Modal>
</>
);
};
useModalTwo
Advanced hook for modal management with render props.
Location: components/modalTwo/useModalTwo.tsx
Usage
import { useModalTwo } from '@proton/components/components/modalTwo/useModalTwo';
const MyComponent = () => {
const [modal, showModal] = useModalTwo(MyModalComponent);
return (
<>
<Button onClick={showModal}>Show Modal</Button>
{modal}
</>
);
};
BasicModal
Simplified modal component for basic use cases.
Location: components/modalTwo/BasicModal.tsx
Usage
import BasicModal from '@proton/components/components/modalTwo/BasicModal';
<BasicModal
isOpen={open}
onClose={() => setOpen(false)}
title="Simple Modal"
>
<p>This is a basic modal with minimal configuration.</p>
</BasicModal>
Best Practices
Modal Management
Use state hooks to manage modal open/close:
const [isOpen, setIsOpen] = useState(false);
const [data, setData] = useState(null);
const handleOpen = (itemData) => {
setData(itemData);
setIsOpen(true);
};
const handleClose = () => {
setIsOpen(false);
setData(null);
};
return (
<Modal open={isOpen} onClose={handleClose}>
<ModalContent>{/* Use data here */}</ModalContent>
</Modal>
);
Accessibility
// Provide meaningful titles
<ModalHeader title="Delete Account" />
// Use proper button roles and labels
<ModalFooter>
<Button onClick={onClose} aria-label="Cancel deletion">
Cancel
</Button>
<Button onClick={onDelete} color="danger" aria-label="Confirm deletion">
Delete
</Button>
</ModalFooter>
Size Selection
xsmall: Simple confirmations, alerts
small: Basic forms, short content
medium: Standard forms, moderate content (default)
large: Complex forms, detailed content
xlarge: Very complex content
full: Full-screen experiences
// Confirmation dialog
<Modal size="small" />
// Standard form
<Modal size="medium" />
// Complex settings
<Modal size="large" />
Loading States
const [loading, setLoading] = useState(false);
const handleSubmit = async () => {
setLoading(true);
try {
await saveData();
onClose();
} catch (error) {
// Handle error
} finally {
setLoading(false);
}
};
return (
<Modal open={open} onClose={onClose}>
<ModalHeader title="Save Changes" />
<ModalContent>
{/* Form content */}
</ModalContent>
<ModalFooter>
<Button disabled={loading} onClick={onClose}>Cancel</Button>
<Button loading={loading} onClick={handleSubmit}>Save</Button>
</ModalFooter>
</Modal>
);
Preventing Accidental Closes
<Modal
open={hasUnsavedChanges}
onClose={handleAttemptClose}
disableCloseOnEscape={hasUnsavedChanges}
onBackdropClick={hasUnsavedChanges ? undefined : onClose}
>
{/* Modal content */}
</Modal>
Common Patterns
Confirmation Modal
const ConfirmationModal = ({ open, onClose, onConfirm, title, message }) => {
return (
<Modal open={open} onClose={onClose} size="small">
<ModalHeader title={title} />
<ModalContent>
<p>{message}</p>
</ModalContent>
<ModalFooter>
<Button onClick={onClose} color="weak">Cancel</Button>
<Button onClick={onConfirm} color="danger">Confirm</Button>
</ModalFooter>
</Modal>
);
};
Multi-Step Modal
const MultiStepModal = ({ open, onClose }) => {
const [step, setStep] = useState(1);
return (
<Modal open={open} onClose={onClose}>
<ModalHeader title={`Step ${step} of 3`} />
<ModalContent>
{step === 1 && <Step1 />}
{step === 2 && <Step2 />}
{step === 3 && <Step3 />}
</ModalContent>
<ModalFooter>
{step > 1 && (
<Button onClick={() => setStep(step - 1)}>Back</Button>
)}
{step < 3 ? (
<Button onClick={() => setStep(step + 1)} color="norm">Next</Button>
) : (
<Button onClick={handleFinish} color="norm">Finish</Button>
)}
</ModalFooter>
</Modal>
);
};
Source Code
View source:
- Modal:
packages/components/components/modalTwo/Modal.tsx:1
- ModalHeader:
packages/components/components/modalTwo/ModalHeader.tsx:1
- useModalState:
packages/components/components/modalTwo/useModalState.ts:1