apps/design-system/content/docs/ui-patterns/modality.mdx
Modal elements interrupt the user’s current task to ask for input, a decision, or focused attention. They appear at the top of the visual stack and (by default) render everything beneath them inactive.
Given their highly interruptive nature, modal elements should be used sparingly. Common use cases include:
We have two main ways of handling modality:
As a general rule: use dialogs for short, focused tasks and use sheets for longer forms or more detailed views.
Dialogs are centered overlays used for short, focused tasks. All dialogs should follow these best practices:
There are quite a few dialog components, each suited to a different task or context:
Alert Dialog is used to confirm or acknowledge a critical action with a single, short paragraph and a clear decision.
<ComponentPreview name="alert-dialog-demo" />Text Confirm Dialog adds a deliberate speed bump for highly destructive actions by requiring the user to type an exact confirmation string before proceeding. The confirm action remains disabled until the input matches.
<ComponentPreview name="text-confirm-dialog-demo" />Confirmation Modal is a convenience wrapper for less-critical confirmations that require more than a single paragraph, such as additional context, callouts, or simple form elements.
<ComponentPreview name="confirmation-modal-demo" />Dialog is a general-purpose modal for bespoke flows such as forms, pickers, or non-critical interactions where dismissal is acceptable.
<ComponentPreview name="dialog-demo" />Sheets are dialogs presented as side panels. Use them for content that is larger than a few fields, or when a centered dialog would feel cramped.
Sheet is modal by default, blocking interaction with the underlying page.
<ComponentPreview name="sheet-demo" />When a dialog or sheet contains a form, keep all normal dismissal affordances enabled (backdrop click, Escape key, close icon, and footer Cancel button).
Decision flow:
Keep editing returns to the form.Discard changes closes and resets the form.Implementation checklist:
onOpenChange.Cancel through the same close guard.Cancel non-destructive; use Discard/Discard changes for one-click destructive exits.Studio implementation (preferred in Studio code):
import { DiscardChangesConfirmationDialog } from 'components/ui-patterns/Dialogs/DiscardChangesConfirmationDialog'
import { useConfirmOnClose } from 'hooks/ui/useConfirmOnClose'
const form = useForm(...)
// Always destructure formState values otherwise they won't be updated
// See https://react-hook-form.com/docs/useform/formstate
const { isDirty } = form.formState
const { confirmOnClose, handleOpenChange, modalProps } = useConfirmOnClose({
checkIsDirty: () => isDirty,
onClose,
})
<Sheet open={visible} onOpenChange={handleOpenChange}>
...
<Button type="default" onClick={confirmOnClose}>
Cancel
</Button>
...
<DiscardChangesConfirmationDialog {...modalProps} />
</Sheet>
Generic implementation (outside Studio):
AlertDialog.confirmOnClose, handleOpenChange, modalProps).