Modal
An overlay dialog that focuses the user's attention on a single task or piece of information. The page behind is blocked from interaction until the modal is dismissed.
When to use
Do
- ✓Use for focused tasks that require the user's full attention — confirmations, forms, alerts.
- ✓Keep modal content concise. If content exceeds a screen, reconsider the pattern.
- ✓Provide a clear dismiss action: the × button, a Cancel button in the footer, or both.
- ✓Return focus to the element that triggered the modal on close.
- ✓Use the blur backdrop when the modal is triggered by a user action (click, keyboard).
- ✓Use the shading backdrop for system-initiated modals (session timeout, cookie consent).
Don't
- ✕Don't open a modal on page load without user interaction — this is disorienting.
- ✕Don't use modals for simple notifications; use inline-notification or toast instead.
- ✕Don't nest modals inside other modals.
- ✕Don't disable the dismiss button without providing another way to close.
- ✕Don't use disableBackdropClick for routine modals — it reduces perceived control.
- ✕Don't put long multi-step flows in a single modal; consider a flyout or separate page.
Controlled pattern
The modal is a controlled component. The consumer owns the open state and must set it to false in response to the dismiss event.
<!-- HTML + vanilla JS -->
<diwa-button id="open-btn" variant="primary">Open Modal</diwa-button>
<diwa-modal id="my-modal" heading="Confirm action">
<p>Are you sure you want to proceed?</p>
<div slot="footer">
<diwa-button id="confirm-btn" variant="primary">Confirm</diwa-button>
<diwa-button id="cancel-btn" variant="secondary">Cancel</diwa-button>
</div>
</diwa-modal>
<script>
const modal = document.getElementById('my-modal');
document.getElementById('open-btn').addEventListener('click', () => {
modal.open = true;
});
modal.addEventListener('dismiss', () => {
modal.open = false;
});
document.getElementById('confirm-btn').addEventListener('click', () => {
// handle confirm...
modal.open = false;
});
document.getElementById('cancel-btn').addEventListener('click', () => {
modal.open = false;
});
</script>React usage
import { useState } from 'react';
function DeleteConfirmation({ onConfirm }: { onConfirm: () => void }) {
const [open, setOpen] = useState(false);
return (
<>
<diwa-button variant="danger" onClick={() => setOpen(true)}>
Delete item
</diwa-button>
<diwa-modal
open={open}
heading="Delete item"
ondismiss={() => setOpen(false)}
>
<p>This action cannot be undone.</p>
<div slot="footer">
<diwa-button variant="danger" onClick={onConfirm}>
Delete
</diwa-button>
<diwa-button variant="secondary" onClick={() => setOpen(false)}>
Cancel
</diwa-button>
</div>
</diwa-modal>
</>
);
}Backdrop variants
backdrop="blur" (default)
Applies a frosted-glass effect via backdrop-filter: blur. Signals that the content behind is temporarily inaccessible but still present. Use for all user-initiated modals.
backdrop="shading"
Applies a solid semi-transparent scrim. Provides higher contrast between the modal and the background. Use for system-triggered interruptions (cookie consent, auth expiry).
CSS variable overrides
The modal panel dimensions can be overridden per-instance using CSS custom properties:
/* Narrow modal for simple confirmations */
diwa-modal.confirm {
--diwa-modal-width: 400px;
}
/* Wide modal for data-heavy content */
diwa-modal.wide {
--diwa-modal-width: 800px;
--diwa-modal-max-height: 95vh;
}