Diwa Design System

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;
}